├── .gitignore ├── README.md ├── angular-storybook ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── projects │ └── components │ │ ├── .eslintrc.json │ │ ├── .storybook │ │ ├── main.ts │ │ ├── preview.ts │ │ ├── tsconfig.json │ │ └── typings.d.ts │ │ ├── README.md │ │ ├── documentation.json │ │ ├── ng-package.json │ │ ├── package.json │ │ ├── src │ │ ├── lib │ │ │ ├── components.component.spec.ts │ │ │ ├── components.component.ts │ │ │ ├── components.module.ts │ │ │ ├── components.service.spec.ts │ │ │ ├── components.service.ts │ │ │ └── counter │ │ │ │ ├── counter.component.css │ │ │ │ ├── counter.component.html │ │ │ │ ├── counter.component.spec.ts │ │ │ │ ├── counter.component.stories.ts │ │ │ │ └── counter.component.ts │ │ ├── public-api.ts │ │ └── stories │ │ │ ├── .eslintrc.json │ │ │ └── Introduction.mdx │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.lib.prod.json │ │ └── tsconfig.spec.json ├── storybook.png └── tsconfig.json ├── buildkit-parallelization ├── .dockerignore ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── Dockerfile ├── README.md ├── assets │ └── pipeline.png ├── checkstyle.xml ├── docker-compose.yml ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── example │ │ ├── Main.java │ │ ├── exception │ │ ├── AppException.java │ │ └── ConfigAppException.java │ │ ├── service │ │ └── SignService.java │ │ └── servlet │ │ └── SignServlet.java │ └── test │ └── java │ └── org │ └── example │ └── service │ └── SignServiceTest.java ├── db-for-testers ├── .gitignore ├── README.md └── docker-entrypoint-initdb.d │ ├── data.sql │ └── schema.sql ├── graalvm-native-image-static ├── .gitignore ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── example │ ├── Main.java │ └── server │ └── Server.java └── gradle-application ├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main └── java └── org └── example ├── Main.java ├── dto ├── HealthResponseDto.java └── MainResponseDto.java └── serlvet ├── HealthServlet.java └── MainServlet.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | NOTES.md 3 | Dockerfile.done 4 | docker-compose.done.yml 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevOps Cases от Slurm 2 | 3 | В этом репозитории мы собрали интересные кейсы, с которыми сами регулярно сталкиваемся в работе 4 | 5 | Вы можете использовать эти кейсы для прокачки ваших скиллов по интересным вам направлениям 6 | 7 | Сами кейсы представлены в том виде, в котором они формулируются в реальной жизни, при этом: 8 | 1. Из кейсов убрано всё лишнее (т.е. и код, и сборка сильно упрощены) 9 | 2. Даны для удобства некоторые ссылки и временами даже подсказки 10 | 11 | **Важно**: это не набор задач для пошагового выполнения! Это именно набор кейсов, для разбора которых вам, возможно, придётся: 12 | - дополнительно изучить используемые в проекте технологии и их особенности 13 | - поковыряться в настройках и поэкспериментировать с ними 14 | 15 | ## Сборка контейнеров 16 | 17 | * [BuildKit Parallelization](buildkit-parallelization/) 18 | * [Angular Storybook](angular-storybook/) 19 | * [Gradle Application](gradle-application/) 20 | * [GraalVM Native Image - Static](graalvm-native-image-static/) 21 | * [DB for Testers](db-for-testers/) 22 | -------------------------------------------------------------------------------- /angular-storybook/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /angular-storybook/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/recommended", 14 | "plugin:@angular-eslint/recommended", 15 | "plugin:@angular-eslint/template/process-inline-templates" 16 | ], 17 | "rules": { 18 | "@angular-eslint/directive-selector": [ 19 | "error", 20 | { 21 | "type": "attribute", 22 | "prefix": "lib", 23 | "style": "camelCase" 24 | } 25 | ], 26 | "@angular-eslint/component-selector": [ 27 | "error", 28 | { 29 | "type": "element", 30 | "prefix": "lib", 31 | "style": "kebab-case" 32 | } 33 | ] 34 | } 35 | }, 36 | { 37 | "files": [ 38 | "*.html" 39 | ], 40 | "extends": [ 41 | "plugin:@angular-eslint/template/recommended" 42 | ], 43 | "rules": {} 44 | } 45 | ], 46 | "extends": [ 47 | "plugin:storybook/recommended" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /angular-storybook/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | # See http://help.github.com/ignore-files/ for more about ignoring files. 4 | storybook-static/ 5 | 6 | # Compiled output 7 | /dist 8 | /tmp 9 | /out-tsc 10 | /bazel-out 11 | 12 | # Node 13 | /node_modules 14 | npm-debug.log 15 | yarn-error.log 16 | 17 | # IDEs and editors 18 | .idea/ 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # Visual Studio Code 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # Miscellaneous 35 | /.angular/cache 36 | .sass-cache/ 37 | /connect.lock 38 | /coverage 39 | /libpeerconnection.log 40 | testem.log 41 | /typings 42 | 43 | # System files 44 | .DS_Store 45 | Thumbs.db 46 | -------------------------------------------------------------------------------- /angular-storybook/README.md: -------------------------------------------------------------------------------- 1 | Теги: `Docker` `JS` `TypeScript` `Angular` `Storybook` `NPM` 2 | 3 | # Angular Storybook 4 | 5 | ## Задача 6 | 7 | Есть проект на Angular (исходники хранятся в этом каталоге), который представляет собой библиотеку компонентов одной из [дизайн-систем](https://tilda.education/courses/web-design/designsystem/) 8 | 9 | Необходимо упаковать витрину-компонентов в Docker-образ, используя в качестве базы конечного образа [Nginx](https://hub.docker.com/_/nginx) версии 1.22 (не alpine и не perl) 10 | 11 | Для промежуточных этапов (сборка, тестирование, линтинг) необходимо использовать [Node 18-buster-slim](https://hub.docker.com/_/node) 12 | 13 | **Важно**: нужно упаковывать не сам проект, а [витрину-компонентов на базе Storybook](https://storybook.js.org) 14 | 15 | ## Формулировка разработчика 16 | 17 | Разработчик сказал следующее* (дословно): 18 | > Мы делаем библиотеку компонентов на Angular, которая должна использоваться всеми проектами внутри компании 19 | > 20 | > Чтобы остальные понимали, как ею пользоваться, мы подготовили [Storybook](https://storybook.js.org) 21 | > 22 | > Теперь нам нужно упаковать всё в Docker так, чтобы при запуске Docker-контейнера люди могли увидеть Storybook 23 | > 24 | > Чтобы запустить Storybook локально, нужно выполнить команду `npm run storybook`, чтобы собрать его: `npm run build-storybook` 25 | > 26 | > Вот так примерно выглядит Storybook: 27 | > 28 | > ![Storybook](storybook.png) 29 | > 30 | > **Важно**: 31 | > 1. У нас есть авто-тесты, с UI они запускаются просто командой `npm test` (`Ctrl + C` для остановки) 32 | > 2. Мы придерживаемся определённого CodeStyle, поэтому проверяем стиль кода через `npm run lint` 33 | > 34 | 35 | *Лирическое отступление*: к сожалению, часто бывает, что разработчики знают, как использовать свои инструменты в режиме разработки, но не знают, как использовать "за пределами разработки", например, как опубликовать тот самый Storybook, поэтому с этим придётся разбираться самостоятельно 36 | 37 | Примечание*: мы обещали, что файлов будет немного (до 10 значащих), поэтому явно указываем, куда смотреть: 38 | 1. [`package.json`](package.json) 39 | 2. [`.gitignore`](.gitignore) 40 | 41 | В других файлах ничего смотреть не нужно, кроме того, не нужно вообще что-то в файлах проекта менять (кроме добавления собственного `Dockerfile` и `.dockerignore`) 42 | 43 | Что нужно сделать: 44 | > 1. Упаковать витрину в Docker-образ 45 | > 2. Выложить всё в виде публичного образа на GHCR* (GitHub Container Registry), чтобы мы могли сами затестить и переиспользовать 46 | 47 | Примечание*: GHCR приведён лишь в качестве примера, вы можете использовать любой реестр на собственное усмотрение 48 | 49 | **Важно**: мы хотим запускать и авто-тесты, и проверку стиля кода 50 | 51 | Чего не нужно делать (дословно): 52 | > Никаких `entrypoint.sh` и других sh-скриптов писать не нужно 53 | > 54 | > Переделывать приложение и конфигурационные файлы вроде `package.json` тоже не нужно, настраивайте запуск всего через: 55 | > 56 | > 1. Установку необходимых переменных окружения 57 | > 58 | > 2. Передачу флагов в соответствующие команды, например, в `npm test` 59 | > 60 | 61 | **Важно**: никакие конфигурации Karma и т.д. создавать не нужно (достаточно установки пары переменных окружения и передачи нескольких параметров) 62 | 63 | ## Реализация 64 | 65 | В качестве реализации CI/CD пайплайна и Docker Registry можно использовать любые, например (из облачных и бесплатных), [GitHub Actions](https://docs.github.com/en/actions) и [GitHub Packages](https://docs.github.com/packages) 66 | -------------------------------------------------------------------------------- /angular-storybook/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "components": { 7 | "projectType": "library", 8 | "root": "projects/components", 9 | "sourceRoot": "projects/components/src", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:ng-packagr", 14 | "options": { 15 | "project": "projects/components/ng-package.json" 16 | }, 17 | "configurations": { 18 | "production": { 19 | "tsConfig": "projects/components/tsconfig.lib.prod.json" 20 | }, 21 | "development": { 22 | "tsConfig": "projects/components/tsconfig.lib.json" 23 | } 24 | }, 25 | "defaultConfiguration": "production" 26 | }, 27 | "test": { 28 | "builder": "@angular-devkit/build-angular:karma", 29 | "options": { 30 | "tsConfig": "projects/components/tsconfig.spec.json", 31 | "polyfills": [ 32 | "zone.js", 33 | "zone.js/testing" 34 | ] 35 | } 36 | }, 37 | "lint": { 38 | "builder": "@angular-eslint/builder:lint", 39 | "options": { 40 | "lintFilePatterns": [ 41 | "projects/components/**/*.ts", 42 | "projects/components/**/*.html" 43 | ] 44 | } 45 | }, 46 | "storybook": { 47 | "builder": "@storybook/angular:start-storybook", 48 | "options": { 49 | "configDir": "projects/components/.storybook", 50 | "browserTarget": "components:build", 51 | "compodoc": true, 52 | "compodocArgs": [ 53 | "-e", 54 | "json", 55 | "-d", 56 | "projects/components" 57 | ], 58 | "port": 6006 59 | } 60 | }, 61 | "build-storybook": { 62 | "builder": "@storybook/angular:build-storybook", 63 | "options": { 64 | "configDir": "projects/components/.storybook", 65 | "browserTarget": "components:build", 66 | "compodoc": true, 67 | "compodocArgs": [ 68 | "-e", 69 | "json", 70 | "-d", 71 | "projects/components" 72 | ], 73 | "outputDir": "storybook-static" 74 | } 75 | } 76 | } 77 | } 78 | }, 79 | "cli": { 80 | "schematicCollections": [ 81 | "@angular-eslint/schematics" 82 | ] 83 | } 84 | } -------------------------------------------------------------------------------- /angular-storybook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components-storybook", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "storybook": "ng run components:storybook", 12 | "build-storybook": "ng run components:build-storybook" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "^15.2.0", 17 | "@angular/common": "^15.2.0", 18 | "@angular/compiler": "^15.2.0", 19 | "@angular/core": "^15.2.0", 20 | "@angular/forms": "^15.2.0", 21 | "@angular/platform-browser": "^15.2.0", 22 | "@angular/platform-browser-dynamic": "^15.2.0", 23 | "@angular/router": "^15.2.0", 24 | "rxjs": "~7.8.0", 25 | "tslib": "^2.3.0", 26 | "zone.js": "~0.12.0" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "^15.2.4", 30 | "@angular-eslint/builder": "15.2.1", 31 | "@angular-eslint/eslint-plugin": "15.2.1", 32 | "@angular-eslint/eslint-plugin-template": "15.2.1", 33 | "@angular-eslint/schematics": "15.2.1", 34 | "@angular-eslint/template-parser": "15.2.1", 35 | "@angular/cli": "~15.2.4", 36 | "@angular/compiler-cli": "^15.2.0", 37 | "@compodoc/compodoc": "^1.1.19", 38 | "@storybook/addon-essentials": "^7.0.0-rc.8", 39 | "@storybook/addon-interactions": "^7.0.0-rc.8", 40 | "@storybook/addon-links": "^7.0.0-rc.8", 41 | "@storybook/angular": "^7.0.0-rc.8", 42 | "@storybook/blocks": "^7.0.0-rc.8", 43 | "@storybook/testing-library": "^0.0.14-next.1", 44 | "@types/jasmine": "~4.3.0", 45 | "@typescript-eslint/eslint-plugin": "5.48.2", 46 | "@typescript-eslint/parser": "5.48.2", 47 | "eslint": "^8.33.0", 48 | "eslint-plugin-storybook": "^0.6.11", 49 | "jasmine-core": "~4.5.0", 50 | "karma": "~6.4.0", 51 | "karma-chrome-launcher": "~3.1.0", 52 | "karma-coverage": "~2.2.0", 53 | "karma-jasmine": "~5.1.0", 54 | "karma-jasmine-html-reporter": "~2.0.0", 55 | "ng-packagr": "^15.2.2", 56 | "react": "^18.2.0", 57 | "react-dom": "^18.2.0", 58 | "storybook": "^7.0.0-rc.8", 59 | "typescript": "~4.9.4" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "lib", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "lib", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": [ 32 | "*.html" 33 | ], 34 | "rules": {} 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from "@storybook/angular"; 2 | const config: StorybookConfig = { 3 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], 4 | addons: [ 5 | "@storybook/addon-links", 6 | "@storybook/addon-essentials", 7 | "@storybook/addon-interactions", 8 | ], 9 | framework: { 10 | name: "@storybook/angular", 11 | options: {}, 12 | }, 13 | docs: { 14 | autodocs: "tag", 15 | }, 16 | core: { 17 | disableTelemetry: true, 18 | }, 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from "@storybook/angular"; 2 | import { setCompodocJson } from "@storybook/addon-docs/angular"; 3 | import docJson from "../documentation.json"; 4 | setCompodocJson(docJson); 5 | 6 | const preview: Preview = { 7 | parameters: { 8 | actions: { argTypesRegex: "^on[A-Z].*" }, 9 | controls: { 10 | matchers: { 11 | color: /(background|color)$/i, 12 | date: /Date$/, 13 | }, 14 | }, 15 | }, 16 | }; 17 | 18 | export default preview; 19 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/.storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.lib.json", 3 | "compilerOptions": { 4 | "types": ["node"], 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true 7 | }, 8 | "exclude": ["../src/test.ts", "../src/**/*.spec.ts"], 9 | "include": ["../src/**/*", "./preview.ts"], 10 | "files": ["./typings.d.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/.storybook/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.md' { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/README.md: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.2.0. 4 | 5 | ## Code scaffolding 6 | 7 | Run `ng generate component component-name --project components` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project components`. 8 | > Note: Don't forget to add `--project components` or else it will be added to the default project in your `angular.json` file. 9 | 10 | ## Build 11 | 12 | Run `ng build components` to build the project. The build artifacts will be stored in the `dist/` directory. 13 | 14 | ## Publishing 15 | 16 | After building your library with `ng build components`, go to the dist folder `cd dist/components` and run `npm publish`. 17 | 18 | ## Running unit tests 19 | 20 | Run `ng test components` to execute the unit tests via [Karma](https://karma-runner.github.io). 21 | 22 | ## Further help 23 | 24 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 25 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/documentation.json: -------------------------------------------------------------------------------- 1 | { 2 | "pipes": [], 3 | "interfaces": [], 4 | "injectables": [ 5 | { 6 | "name": "ComponentsService", 7 | "id": "injectable-ComponentsService-1a68e5ab8060ac18996868bd337f39aafb46491a3144134c587d69dfd820cf07ba62295c1f88df06847e3ef997966d1275f1d740fea7c0054af98562a751c5c8", 8 | "file": "projects/components/src/lib/components.service.ts", 9 | "properties": [], 10 | "methods": [], 11 | "deprecated": false, 12 | "deprecationMessage": "", 13 | "description": "", 14 | "rawdescription": "\n", 15 | "sourceCode": "import { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ComponentsService {\n}\n", 16 | "type": "injectable" 17 | } 18 | ], 19 | "guards": [], 20 | "interceptors": [], 21 | "classes": [], 22 | "directives": [], 23 | "components": [ 24 | { 25 | "name": "ComponentsComponent", 26 | "id": "component-ComponentsComponent-9ab62237a9b0bb4002405ad11127778bede646e1f7acafd99d9501b37bd21cd6b3ef9f89e57bab0c22f531acb1df42d8f2e139a6066634bf5afca0f4c9fe0106", 27 | "file": "projects/components/src/lib/components.component.ts", 28 | "encapsulation": [], 29 | "entryComponents": [], 30 | "inputs": [], 31 | "outputs": [], 32 | "providers": [], 33 | "selector": "lib-components", 34 | "styleUrls": [], 35 | "styles": [], 36 | "template": "

\n components works!\n

\n", 37 | "templateUrl": [], 38 | "viewProviders": [], 39 | "inputsClass": [], 40 | "outputsClass": [], 41 | "propertiesClass": [], 42 | "methodsClass": [], 43 | "deprecated": false, 44 | "deprecationMessage": "", 45 | "hostBindings": [], 46 | "hostListeners": [], 47 | "description": "", 48 | "rawdescription": "\n", 49 | "type": "component", 50 | "sourceCode": "import { Component } from '@angular/core';\n\n@Component({\n selector: 'lib-components',\n template: `\n

\n components works!\n

\n `,\n styles: [\n ]\n})\nexport class ComponentsComponent {\n\n}\n", 51 | "assetsDirs": [], 52 | "styleUrlsData": "", 53 | "stylesData": "" 54 | }, 55 | { 56 | "name": "CounterComponent", 57 | "id": "component-CounterComponent-dec7b5f254e9a70b3d288a731a015e80650bffd879b0a1e82602958e0cf740d2879ae92e4b810aa8e3b7927c7146a7f7f768bc6f8fef3630e2f9b766a0945adf", 58 | "file": "projects/components/src/lib/counter/counter.component.ts", 59 | "encapsulation": [], 60 | "entryComponents": [], 61 | "inputs": [], 62 | "outputs": [], 63 | "providers": [], 64 | "selector": "lib-counter", 65 | "styleUrls": [ 66 | "./counter.component.css" 67 | ], 68 | "styles": [], 69 | "templateUrl": [ 70 | "./counter.component.html" 71 | ], 72 | "viewProviders": [], 73 | "inputsClass": [ 74 | { 75 | "name": "disabled", 76 | "defaultValue": "false", 77 | "deprecated": false, 78 | "deprecationMessage": "", 79 | "line": 14, 80 | "type": "boolean", 81 | "decorators": [] 82 | }, 83 | { 84 | "name": "prefix", 85 | "defaultValue": "'lib-counter'", 86 | "deprecated": false, 87 | "deprecationMessage": "", 88 | "line": 9, 89 | "type": "string", 90 | "decorators": [] 91 | }, 92 | { 93 | "name": "primary", 94 | "defaultValue": "true", 95 | "deprecated": false, 96 | "deprecationMessage": "", 97 | "line": 10, 98 | "type": "boolean", 99 | "decorators": [] 100 | }, 101 | { 102 | "name": "size", 103 | "defaultValue": "'medium'", 104 | "deprecated": false, 105 | "deprecationMessage": "", 106 | "line": 11, 107 | "type": "\"small\" | \"medium\" | \"large\"", 108 | "decorators": [] 109 | }, 110 | { 111 | "name": "testid", 112 | "defaultValue": "''", 113 | "deprecated": false, 114 | "deprecationMessage": "", 115 | "line": 12, 116 | "type": "string", 117 | "decorators": [] 118 | }, 119 | { 120 | "name": "type", 121 | "defaultValue": "''", 122 | "deprecated": false, 123 | "deprecationMessage": "", 124 | "line": 13, 125 | "type": "string", 126 | "decorators": [] 127 | }, 128 | { 129 | "name": "value", 130 | "defaultValue": "0", 131 | "deprecated": false, 132 | "deprecationMessage": "", 133 | "line": 15, 134 | "type": "number", 135 | "decorators": [] 136 | } 137 | ], 138 | "outputsClass": [ 139 | { 140 | "name": "increase", 141 | "defaultValue": "new EventEmitter()", 142 | "deprecated": false, 143 | "deprecationMessage": "", 144 | "line": 16, 145 | "type": "EventEmitter" 146 | } 147 | ], 148 | "propertiesClass": [], 149 | "methodsClass": [ 150 | { 151 | "name": "onClick", 152 | "args": [ 153 | { 154 | "name": "$event", 155 | "type": "Event", 156 | "deprecated": false, 157 | "deprecationMessage": "" 158 | } 159 | ], 160 | "optional": false, 161 | "returnType": "void", 162 | "typeParameters": [], 163 | "line": 22, 164 | "deprecated": false, 165 | "deprecationMessage": "", 166 | "jsdoctags": [ 167 | { 168 | "name": "$event", 169 | "type": "Event", 170 | "deprecated": false, 171 | "deprecationMessage": "", 172 | "tagName": { 173 | "text": "param" 174 | } 175 | } 176 | ] 177 | } 178 | ], 179 | "deprecated": false, 180 | "deprecationMessage": "", 181 | "hostBindings": [], 182 | "hostListeners": [], 183 | "description": "", 184 | "rawdescription": "\n", 185 | "type": "component", 186 | "sourceCode": "import { Component, EventEmitter, Input, Output } from '@angular/core';\n\n@Component({\n selector: 'lib-counter',\n templateUrl: './counter.component.html',\n styleUrls: ['./counter.component.css']\n})\nexport class CounterComponent {\n @Input() prefix = 'lib-counter';\n @Input() primary = true;\n @Input() size : 'small' | 'medium' | 'large' = 'medium';\n @Input() testid = '';\n @Input() type = '';\n @Input() disabled = false;\n @Input() value = 0;\n @Output() increase = new EventEmitter();\n\n public get classes(): string[] {\n return [this.prefix, `${this.prefix}--${this.size}`, `${this.prefix}--${this.primary ? 'primary' : 'secondary'}`];\n }\n\n onClick($event: Event) {\n $event.preventDefault();\n this.value++;\n console.log('click');\n this.increase.emit(this.value);\n }\n}\n", 187 | "assetsDirs": [], 188 | "styleUrlsData": [ 189 | { 190 | "data": ".lib-counter {\n font-family: Helvetica, Arial, sans-serif;\n font-weight: 700;\n border-radius: 5px;\n cursor: pointer;\n display: inline-block;\n line-height: 1;\n}\n.lib-counter--primary {\n color: white;\n background-color: #8a73fc;\n border: 1px solid #8a73fc;\n}\n.lib-counter--secondary {\n color: #172b4d;\n background-color: transparent;\n border: 1px solid #172b4d;\n}\n.lib-counter--small {\n font-size: 12px;\n padding: 10px 16px;\n}\n.lib-counter--medium {\n font-size: 14px;\n padding: 11px 20px;\n}\n.lib-counter--large {\n font-size: 16px;\n padding: 12px 24px;\n}\n", 191 | "styleUrl": "./counter.component.css" 192 | } 193 | ], 194 | "stylesData": "", 195 | "accessors": { 196 | "classes": { 197 | "name": "classes", 198 | "getSignature": { 199 | "name": "classes", 200 | "type": "[]", 201 | "returnType": "string[]", 202 | "line": 18 203 | } 204 | } 205 | }, 206 | "templateData": "\n\t{{value}}\n" 207 | } 208 | ], 209 | "modules": [ 210 | { 211 | "name": "ComponentsModule", 212 | "id": "module-ComponentsModule-924be5c2c6949aeeed9a9848432fce6586816afa3ae2cd3df16a25531ff649d8148ee6c33be3454bf97abde0ea1c5a746d39eb875b0e0fdd60687b3e45c430bd", 213 | "description": "", 214 | "deprecationMessage": "", 215 | "deprecated": false, 216 | "file": "projects/components/src/lib/components.module.ts", 217 | "methods": [], 218 | "sourceCode": "import { NgModule } from '@angular/core';\nimport { ComponentsComponent } from './components.component';\nimport { CounterComponent } from './counter/counter.component';\n\n\n\n@NgModule({\n declarations: [\n ComponentsComponent,\n CounterComponent\n ],\n imports: [\n ],\n exports: [\n ComponentsComponent\n ]\n})\nexport class ComponentsModule { }\n", 219 | "children": [ 220 | { 221 | "type": "providers", 222 | "elements": [] 223 | }, 224 | { 225 | "type": "declarations", 226 | "elements": [ 227 | { 228 | "name": "ComponentsComponent" 229 | }, 230 | { 231 | "name": "CounterComponent" 232 | } 233 | ] 234 | }, 235 | { 236 | "type": "imports", 237 | "elements": [] 238 | }, 239 | { 240 | "type": "exports", 241 | "elements": [ 242 | { 243 | "name": "ComponentsComponent" 244 | } 245 | ] 246 | }, 247 | { 248 | "type": "bootstrap", 249 | "elements": [] 250 | }, 251 | { 252 | "type": "classes", 253 | "elements": [] 254 | } 255 | ] 256 | } 257 | ], 258 | "miscellaneous": { 259 | "variables": [ 260 | { 261 | "name": "LargeCounter", 262 | "ctype": "miscellaneous", 263 | "subtype": "variable", 264 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 265 | "deprecated": false, 266 | "deprecationMessage": "", 267 | "type": "Story", 268 | "defaultValue": "{\n args: {\n size: 'large',\n value: 999,\n },\n}" 269 | }, 270 | { 271 | "name": "meta", 272 | "ctype": "miscellaneous", 273 | "subtype": "variable", 274 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 275 | "deprecated": false, 276 | "deprecationMessage": "", 277 | "type": "Meta", 278 | "defaultValue": "{\n title: 'Example/CounterComponent',\n component: CounterComponent,\n tags: ['autodocs'],\n render: (args: CounterComponent) => ({\n props: {\n ...args,\n },\n }),\n}" 279 | }, 280 | { 281 | "name": "preview", 282 | "ctype": "miscellaneous", 283 | "subtype": "variable", 284 | "file": "projects/components/.storybook/preview.ts", 285 | "deprecated": false, 286 | "deprecationMessage": "", 287 | "type": "Preview", 288 | "defaultValue": "{\n parameters: {\n actions: { argTypesRegex: \"^on[A-Z].*\" },\n controls: {\n matchers: {\n color: /(background|color)$/i,\n date: /Date$/,\n },\n },\n },\n}" 289 | }, 290 | { 291 | "name": "PrimaryCounter", 292 | "ctype": "miscellaneous", 293 | "subtype": "variable", 294 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 295 | "deprecated": false, 296 | "deprecationMessage": "", 297 | "type": "Story", 298 | "defaultValue": "{\n args: {\n primary: true,\n value: 1,\n },\n}" 299 | }, 300 | { 301 | "name": "SecondaryCounter", 302 | "ctype": "miscellaneous", 303 | "subtype": "variable", 304 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 305 | "deprecated": false, 306 | "deprecationMessage": "", 307 | "type": "Story", 308 | "defaultValue": "{\n args: {\n primary: false,\n value: 1,\n },\n}" 309 | }, 310 | { 311 | "name": "SmallCounter", 312 | "ctype": "miscellaneous", 313 | "subtype": "variable", 314 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 315 | "deprecated": false, 316 | "deprecationMessage": "", 317 | "type": "Story", 318 | "defaultValue": "{\n args: {\n size: 'small',\n value: 9,\n },\n}" 319 | } 320 | ], 321 | "functions": [], 322 | "typealiases": [ 323 | { 324 | "name": "Story", 325 | "ctype": "miscellaneous", 326 | "subtype": "typealias", 327 | "rawtype": "StoryObj", 328 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 329 | "deprecated": false, 330 | "deprecationMessage": "", 331 | "description": "", 332 | "kind": 177 333 | } 334 | ], 335 | "enumerations": [], 336 | "groupedVariables": { 337 | "projects/components/src/lib/counter/counter.component.stories.ts": [ 338 | { 339 | "name": "LargeCounter", 340 | "ctype": "miscellaneous", 341 | "subtype": "variable", 342 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 343 | "deprecated": false, 344 | "deprecationMessage": "", 345 | "type": "Story", 346 | "defaultValue": "{\n args: {\n size: 'large',\n value: 999,\n },\n}" 347 | }, 348 | { 349 | "name": "meta", 350 | "ctype": "miscellaneous", 351 | "subtype": "variable", 352 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 353 | "deprecated": false, 354 | "deprecationMessage": "", 355 | "type": "Meta", 356 | "defaultValue": "{\n title: 'Example/CounterComponent',\n component: CounterComponent,\n tags: ['autodocs'],\n render: (args: CounterComponent) => ({\n props: {\n ...args,\n },\n }),\n}" 357 | }, 358 | { 359 | "name": "PrimaryCounter", 360 | "ctype": "miscellaneous", 361 | "subtype": "variable", 362 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 363 | "deprecated": false, 364 | "deprecationMessage": "", 365 | "type": "Story", 366 | "defaultValue": "{\n args: {\n primary: true,\n value: 1,\n },\n}" 367 | }, 368 | { 369 | "name": "SecondaryCounter", 370 | "ctype": "miscellaneous", 371 | "subtype": "variable", 372 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 373 | "deprecated": false, 374 | "deprecationMessage": "", 375 | "type": "Story", 376 | "defaultValue": "{\n args: {\n primary: false,\n value: 1,\n },\n}" 377 | }, 378 | { 379 | "name": "SmallCounter", 380 | "ctype": "miscellaneous", 381 | "subtype": "variable", 382 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 383 | "deprecated": false, 384 | "deprecationMessage": "", 385 | "type": "Story", 386 | "defaultValue": "{\n args: {\n size: 'small',\n value: 9,\n },\n}" 387 | } 388 | ], 389 | "projects/components/.storybook/preview.ts": [ 390 | { 391 | "name": "preview", 392 | "ctype": "miscellaneous", 393 | "subtype": "variable", 394 | "file": "projects/components/.storybook/preview.ts", 395 | "deprecated": false, 396 | "deprecationMessage": "", 397 | "type": "Preview", 398 | "defaultValue": "{\n parameters: {\n actions: { argTypesRegex: \"^on[A-Z].*\" },\n controls: {\n matchers: {\n color: /(background|color)$/i,\n date: /Date$/,\n },\n },\n },\n}" 399 | } 400 | ] 401 | }, 402 | "groupedFunctions": {}, 403 | "groupedEnumerations": {}, 404 | "groupedTypeAliases": { 405 | "projects/components/src/lib/counter/counter.component.stories.ts": [ 406 | { 407 | "name": "Story", 408 | "ctype": "miscellaneous", 409 | "subtype": "typealias", 410 | "rawtype": "StoryObj", 411 | "file": "projects/components/src/lib/counter/counter.component.stories.ts", 412 | "deprecated": false, 413 | "deprecationMessage": "", 414 | "description": "", 415 | "kind": 177 416 | } 417 | ] 418 | } 419 | }, 420 | "routes": [], 421 | "coverage": { 422 | "count": 0, 423 | "status": "low", 424 | "files": [ 425 | { 426 | "filePath": "projects/components/.storybook/preview.ts", 427 | "type": "variable", 428 | "linktype": "miscellaneous", 429 | "linksubtype": "variable", 430 | "name": "preview", 431 | "coveragePercent": 0, 432 | "coverageCount": "0/1", 433 | "status": "low" 434 | }, 435 | { 436 | "filePath": "projects/components/src/lib/components.component.ts", 437 | "type": "component", 438 | "linktype": "component", 439 | "name": "ComponentsComponent", 440 | "coveragePercent": 0, 441 | "coverageCount": "0/1", 442 | "status": "low" 443 | }, 444 | { 445 | "filePath": "projects/components/src/lib/components.service.ts", 446 | "type": "injectable", 447 | "linktype": "injectable", 448 | "name": "ComponentsService", 449 | "coveragePercent": 0, 450 | "coverageCount": "0/1", 451 | "status": "low" 452 | }, 453 | { 454 | "filePath": "projects/components/src/lib/counter/counter.component.stories.ts", 455 | "type": "variable", 456 | "linktype": "miscellaneous", 457 | "linksubtype": "variable", 458 | "name": "LargeCounter", 459 | "coveragePercent": 0, 460 | "coverageCount": "0/1", 461 | "status": "low" 462 | }, 463 | { 464 | "filePath": "projects/components/src/lib/counter/counter.component.stories.ts", 465 | "type": "variable", 466 | "linktype": "miscellaneous", 467 | "linksubtype": "variable", 468 | "name": "meta", 469 | "coveragePercent": 0, 470 | "coverageCount": "0/1", 471 | "status": "low" 472 | }, 473 | { 474 | "filePath": "projects/components/src/lib/counter/counter.component.stories.ts", 475 | "type": "variable", 476 | "linktype": "miscellaneous", 477 | "linksubtype": "variable", 478 | "name": "PrimaryCounter", 479 | "coveragePercent": 0, 480 | "coverageCount": "0/1", 481 | "status": "low" 482 | }, 483 | { 484 | "filePath": "projects/components/src/lib/counter/counter.component.stories.ts", 485 | "type": "variable", 486 | "linktype": "miscellaneous", 487 | "linksubtype": "variable", 488 | "name": "SecondaryCounter", 489 | "coveragePercent": 0, 490 | "coverageCount": "0/1", 491 | "status": "low" 492 | }, 493 | { 494 | "filePath": "projects/components/src/lib/counter/counter.component.stories.ts", 495 | "type": "variable", 496 | "linktype": "miscellaneous", 497 | "linksubtype": "variable", 498 | "name": "SmallCounter", 499 | "coveragePercent": 0, 500 | "coverageCount": "0/1", 501 | "status": "low" 502 | }, 503 | { 504 | "filePath": "projects/components/src/lib/counter/counter.component.ts", 505 | "type": "component", 506 | "linktype": "component", 507 | "name": "CounterComponent", 508 | "coveragePercent": 0, 509 | "coverageCount": "0/10", 510 | "status": "low" 511 | } 512 | ] 513 | } 514 | } -------------------------------------------------------------------------------- /angular-storybook/projects/components/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/components", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /angular-storybook/projects/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^15.2.0", 6 | "@angular/core": "^15.2.0" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.3.0" 10 | }, 11 | "sideEffects": false 12 | } 13 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/lib/components.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ComponentsComponent } from './components.component'; 4 | 5 | describe('ComponentsComponent', () => { 6 | let component: ComponentsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ComponentsComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ComponentsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/lib/components.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'lib-components', 5 | template: ` 6 |

7 | components works! 8 |

9 | `, 10 | styles: [ 11 | ] 12 | }) 13 | export class ComponentsComponent { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/lib/components.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ComponentsComponent } from './components.component'; 3 | import { CounterComponent } from './counter/counter.component'; 4 | 5 | 6 | 7 | @NgModule({ 8 | declarations: [ 9 | ComponentsComponent, 10 | CounterComponent 11 | ], 12 | imports: [ 13 | ], 14 | exports: [ 15 | ComponentsComponent 16 | ] 17 | }) 18 | export class ComponentsModule { } 19 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/lib/components.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ComponentsService } from './components.service'; 4 | 5 | describe('ComponentsService', () => { 6 | let service: ComponentsService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ComponentsService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/lib/components.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class ComponentsService { 7 | } 8 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/lib/counter/counter.component.css: -------------------------------------------------------------------------------- 1 | .lib-counter { 2 | font-family: Helvetica, Arial, sans-serif; 3 | font-weight: 700; 4 | border-radius: 5px; 5 | cursor: pointer; 6 | display: inline-block; 7 | line-height: 1; 8 | } 9 | .lib-counter--primary { 10 | color: white; 11 | background-color: #8a73fc; 12 | border: 1px solid #8a73fc; 13 | } 14 | .lib-counter--secondary { 15 | color: #172b4d; 16 | background-color: transparent; 17 | border: 1px solid #172b4d; 18 | } 19 | .lib-counter--small { 20 | font-size: 12px; 21 | padding: 10px 16px; 22 | } 23 | .lib-counter--medium { 24 | font-size: 14px; 25 | padding: 11px 20px; 26 | } 27 | .lib-counter--large { 28 | font-size: 16px; 29 | padding: 12px 24px; 30 | } 31 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/lib/counter/counter.component.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/lib/counter/counter.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CounterComponent } from './counter.component'; 4 | 5 | describe('CounterComponent', () => { 6 | let component: CounterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CounterComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CounterComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/lib/counter/counter.component.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/angular'; 2 | import { CounterComponent } from './counter.component'; 3 | 4 | const meta: Meta = { 5 | title: 'Example/CounterComponent', 6 | component: CounterComponent, 7 | tags: ['autodocs'], 8 | render: (args: CounterComponent) => ({ 9 | props: { 10 | ...args, 11 | }, 12 | }), 13 | }; 14 | 15 | export default meta; 16 | type Story = StoryObj; 17 | 18 | export const PrimaryCounter: Story = { 19 | args: { 20 | primary: true, 21 | value: 1, 22 | }, 23 | }; 24 | 25 | export const SecondaryCounter: Story = { 26 | args: { 27 | primary: false, 28 | value: 1, 29 | }, 30 | }; 31 | 32 | export const LargeCounter: Story = { 33 | args: { 34 | size: 'large', 35 | value: 999, 36 | }, 37 | }; 38 | 39 | export const SmallCounter: Story = { 40 | args: { 41 | size: 'small', 42 | value: 9, 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/lib/counter/counter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'lib-counter', 5 | templateUrl: './counter.component.html', 6 | styleUrls: ['./counter.component.css'] 7 | }) 8 | export class CounterComponent { 9 | @Input() prefix = 'lib-counter'; 10 | @Input() primary = true; 11 | @Input() size : 'small' | 'medium' | 'large' = 'medium'; 12 | @Input() testid = ''; 13 | @Input() type = ''; 14 | @Input() disabled = false; 15 | @Input() value = 0; 16 | @Output() increase = new EventEmitter(); 17 | 18 | public get classes(): string[] { 19 | return [this.prefix, `${this.prefix}--${this.size}`, `${this.prefix}--${this.primary ? 'primary' : 'secondary'}`]; 20 | } 21 | 22 | onClick($event: Event) { 23 | $event.preventDefault(); 24 | this.value++; 25 | console.log('click'); 26 | this.increase.emit(this.value); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of components 3 | */ 4 | 5 | export * from './lib/components.service'; 6 | export * from './lib/components.component'; 7 | export * from './lib/components.module'; 8 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/stories/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "@typescript-eslint/consistent-type-imports": ["error", { "disallowTypeAnnotations": false }] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/src/stories/Introduction.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/blocks'; 2 | 3 | 4 | 5 | # Welcome to Storybook 6 | 7 | Этот Storybook демонстрирует использование нашей библиотеки компонентов -------------------------------------------------------------------------------- /angular-storybook/projects/components/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/lib", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /angular-storybook/projects/components/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "**/*.spec.ts", 12 | "**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /angular-storybook/storybook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slurmio/devopscases/09b9f14317068252642b162b73b30279e20a2925/angular-storybook/storybook.png -------------------------------------------------------------------------------- /angular-storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "paths": { 7 | "components": [ 8 | "dist/components" 9 | ] 10 | }, 11 | "outDir": "./dist/out-tsc", 12 | "forceConsistentCasingInFileNames": true, 13 | "strict": true, 14 | "noImplicitOverride": true, 15 | "noPropertyAccessFromIndexSignature": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "sourceMap": true, 19 | "declaration": false, 20 | "downlevelIteration": true, 21 | "experimentalDecorators": true, 22 | "moduleResolution": "node", 23 | "importHelpers": true, 24 | "target": "ES2022", 25 | "module": "ES2022", 26 | "useDefineForClassFields": false, 27 | "lib": [ 28 | "ES2022", 29 | "dom" 30 | ] 31 | }, 32 | "angularCompilerOptions": { 33 | "enableI18nLegacyMessageIdFormat": false, 34 | "strictInjectionParameters": true, 35 | "strictInputAccessModifiers": true, 36 | "strictTemplates": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /buildkit-parallelization/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile* 2 | docker-compose*.yml 3 | 4 | assets/ 5 | target/ 6 | !.mvn/wrapper/maven-wrapper.jar 7 | !**/src/main/**/target/ 8 | !**/src/test/**/target/ 9 | 10 | ### IntelliJ IDEA ### 11 | .idea 12 | *.iws 13 | *.iml 14 | *.ipr 15 | 16 | ### Eclipse ### 17 | .apt_generated 18 | .classpath 19 | .factorypath 20 | .project 21 | .settings 22 | .springBeans 23 | .sts4-cache 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | build/ 32 | !**/src/main/**/build/ 33 | !**/src/test/**/build/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | 38 | ### Mac OS, Windows ### 39 | .DS_Store 40 | Thumbs.db* 41 | -------------------------------------------------------------------------------- /buildkit-parallelization/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea 8 | *.iws 9 | *.iml 10 | *.ipr 11 | 12 | ### Eclipse ### 13 | .apt_generated 14 | .classpath 15 | .factorypath 16 | .project 17 | .settings 18 | .springBeans 19 | .sts4-cache 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | 34 | ### Mac OS, Windows ### 35 | .DS_Store 36 | Thumbs.db* -------------------------------------------------------------------------------- /buildkit-parallelization/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slurmio/devopscases/09b9f14317068252642b162b73b30279e20a2925/buildkit-parallelization/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /buildkit-parallelization/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /buildkit-parallelization/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin@sha256:831f00e903bf888787b8ffd1bf5252e5903e1b19d5bc17f7e5b9761ffec11785 AS builder 2 | WORKDIR /opt/build 3 | COPY . . 4 | RUN ./mvnw --no-transfer-progress --batch-mode verify 5 | 6 | FROM eclipse-temurin@sha256:831f00e903bf888787b8ffd1bf5252e5903e1b19d5bc17f7e5b9761ffec11785 AS release 7 | WORKDIR /opt/app 8 | COPY --from=builder /opt/build/target/app.jar . 9 | 10 | ENV APP_SIGN_ALGO HmacSHA256 11 | ENV APP_SIGN_KEY 073b9ce8277c4d70dba2edf33e80f6a2bde0dbd7e5c9d149ccc20ba8b125129b 12 | 13 | USER 1000 14 | 15 | EXPOSE 8080 16 | CMD ["java", "-jar", "app.jar"] -------------------------------------------------------------------------------- /buildkit-parallelization/README.md: -------------------------------------------------------------------------------- 1 | Теги: `Docker` `Java` `Maven` 2 | 3 | # BuildKit Parallelization 4 | 5 | Разработчики проекта хотят ускорить сборку и прохождение проверок (авто-тесты, проверка CodeStyle, зависимостей и т.д.) и обратились к вам, как к эксперту по Docker, системам сборки и прочему (включая выстраивание оптимальных процессов) 6 | 7 | Мотивация: авто-тесты, как и проверка зависимостей на уязвимости (и другие активности), могут проходить достаточно долго, поэтому почему бы их не распараллелить 8 | 9 | **Что нужно сделать**: используя возможности [BuildKit](https://docs.docker.com/build/buildkit/) распараллелить сборку (компиляцию исходников и сборку Uber JAR) и служебные задачи вроде проверки CodeStyle, Unit-тестов и т.д. 10 | 11 | Желаемый вид: 12 | 13 | ![](assets/pipeline.png) 14 | 15 | **Важно**: `pom.xml` редактировать нельзя 16 | 17 | *Q*: почему инструменты верификации нужно запускать после компиляции? 18 | 19 | *A*: некоторые из них (например, SpotBugs) работают на уровне байт-кода, кроме того, проверять стиль кодирования имеет смысл только в случае, если код хотя бы компилируется, поэтому мы упростили задачу и не предлагаем вам детально разбираться в том, что конкретный плагин требует (только исходников или ещё и байт-код) 20 | 21 | ## Инструкции по [сборке проекта](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html) 22 | 23 | Текущая сборка описана в файле [`Dockerfile`](Dockerfile), сборка производится командой: 24 | 25 | ```shell 26 | docker buildx build . 27 | ``` 28 | 29 | Используемые переменные окружения: 30 | * `APP_SIGN_ALGO` — алгоритм генерации [HMAC](https://www.rfc-editor.org/rfc/rfc2104) (в терминах разработчика — "подписи") 31 | * `APP_SIGN_KEY` — ключ для генерации [HMAC](https://www.rfc-editor.org/rfc/rfc2104) (в терминах разработчика — "подписи") 32 | 33 | Используемый порт: `8080` 34 | 35 | Команда для тестирования: 36 | ```shell 37 | curl -X POST http://localhost:8080 -d 'secret' 38 | ``` 39 | 40 | Ожидаемый ответ: 41 | ```shell 42 | b4a5151c4d6939f8df7febe32ac94c3ce0f61112cbe1d8df0e51d7f2c4d9618b 43 | ``` 44 | 45 | ## Подсказки 46 | 47 | Если очень-очень верхнеуровнево, то: 48 | 49 | 1. `./mvnw package` — компиляция, Unit-тесты, сборка Uber JAR 50 | 2. `./mvnw verify` — `package` + проверка CodeStyle, проверка уязвимых зависимостей, поиск багов 51 | 52 | Детали см. в [Maven Default Lifecycle](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#default-lifecycle) 53 | 54 | **Важно**: убедитесь, что вынесенные в "параллельные" stage активности действительно отрабатывают 55 | 56 |
57 | Спойлеры 58 | 59 | Вполне возможно, что BuildKit оптимизирует сборку таким образом, что при определённых условиях "параллельные" stage запускаться не будут 60 |
61 | 62 | ## Реализация 63 | 64 | В качестве реализации CI/CD пайплайна и Docker Registry можно использовать любые, например (из облачных и бесплатных), [GitHub Actions](https://docs.github.com/en/actions) и [GitHub Packages](https://docs.github.com/packages) 65 | 66 | # Разборы 67 | 68 | * 24.10.2023 – Максим Суслов ([Видео на YouTube](https://www.youtube.com/watch?v=VVSLdUfa_Js), [Репо на GitHub](https://github.com/maxx27/slurm-devopscases/tree/buildkit-parallelization-solution)) 69 | -------------------------------------------------------------------------------- /buildkit-parallelization/assets/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slurmio/devopscases/09b9f14317068252642b162b73b30279e20a2925/buildkit-parallelization/assets/pipeline.png -------------------------------------------------------------------------------- /buildkit-parallelization/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 60 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 71 | 72 | 73 | 75 | 76 | 77 | 83 | 84 | 85 | 86 | 89 | 90 | 91 | 92 | 93 | 97 | 98 | 99 | 100 | 101 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 119 | 122 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 170 | 171 | 172 | 174 | 176 | 177 | 178 | 179 | 181 | 182 | 183 | 184 | 186 | 187 | 188 | 189 | 191 | 192 | 193 | 194 | 196 | 197 | 198 | 199 | 201 | 202 | 203 | 204 | 206 | 207 | 208 | 209 | 211 | 212 | 213 | 214 | 216 | 217 | 218 | 219 | 221 | 222 | 223 | 224 | 226 | 227 | 228 | 229 | 231 | 232 | 233 | 234 | 236 | 238 | 240 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 258 | 259 | 260 | 261 | 262 | 263 | 266 | 267 | 268 | 271 | 272 | 273 | 274 | 280 | 281 | 282 | 283 | 287 | 288 | 289 | 290 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 321 | 322 | 323 | 324 | -------------------------------------------------------------------------------- /buildkit-parallelization/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: app 4 | build: 5 | context: . 6 | environment: 7 | - APP_SIGN_ALGO=HmacSHA256 8 | - APP_SIGN_KEY=073b9ce8277c4d70dba2edf33e80f6a2bde0dbd7e5c9d149ccc20ba8b125129b 9 | ports: 10 | - '8080:8080' -------------------------------------------------------------------------------- /buildkit-parallelization/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /buildkit-parallelization/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /buildkit-parallelization/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | sign-service 9 | 1.0.0 10 | 11 | 12 | 17 13 | 17 14 | UTF-8 15 | 16 | 1.18.28 17 | 1.4.11 18 | 1.16.0 19 | 20 | 11.0.6 21 | 5.10.0 22 | 23 | 3.1.2 24 | 3.6.0 25 | 3.3.0 26 | 4.7.3.6 27 | 8.4.0 28 | 29 | checkstyle.xml 30 | 31 | 32 | 33 | 34 | org.projectlombok 35 | lombok 36 | ${lombok.version} 37 | provided 38 | 39 | 40 | ch.qos.logback 41 | logback-classic 42 | ${logback-classic.version} 43 | 44 | 45 | commons-codec 46 | commons-codec 47 | ${commons-codec.version} 48 | 49 | 50 | org.eclipse.jetty 51 | jetty-servlet 52 | ${jetty-servlet.version} 53 | 54 | 55 | org.junit.jupiter 56 | junit-jupiter 57 | ${junit-jupiter.version} 58 | test 59 | 60 | 61 | 62 | 63 | 64 | app 65 | 66 | 67 | maven-surefire-plugin 68 | ${maven-surefire-plugin.version} 69 | 70 | 71 | 72 | maven-assembly-plugin 73 | ${maven-assembly-plugin.version} 74 | 75 | 76 | 77 | org.example.Main 78 | 79 | 80 | 81 | jar-with-dependencies 82 | 83 | false 84 | 85 | 86 | 87 | 88 | single 89 | 90 | package 91 | 92 | 93 | 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-checkstyle-plugin 98 | ${maven-checkstyle-plugin.version} 99 | 100 | 101 | 102 | check 103 | 104 | 105 | 106 | 107 | 108 | 109 | com.github.spotbugs 110 | spotbugs-maven-plugin 111 | ${spotbugs-maven-plugin.version} 112 | 113 | 114 | 115 | check 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | org.owasp 124 | dependency-check-maven 125 | ${dependency-check-maven.version} 126 | 127 | true 128 | 129 | 130 | 131 | 132 | 133 | check 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /buildkit-parallelization/src/main/java/org/example/Main.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import lombok.SneakyThrows; 4 | import org.eclipse.jetty.server.Server; 5 | import org.eclipse.jetty.servlet.ServletHandler; 6 | import org.example.servlet.SignServlet; 7 | 8 | public class Main { 9 | @SneakyThrows 10 | public static void main(String[] args) { 11 | final Server server = new Server(8080); 12 | 13 | final ServletHandler handler = new ServletHandler(); 14 | // no initialization for servlet/service 15 | handler.addServletWithMapping(SignServlet.class, "/"); 16 | server.setHandler(handler); 17 | 18 | server.start(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /buildkit-parallelization/src/main/java/org/example/exception/AppException.java: -------------------------------------------------------------------------------- 1 | package org.example.exception; 2 | 3 | public class AppException extends RuntimeException { 4 | public AppException() { 5 | } 6 | 7 | public AppException(final String message) { 8 | super(message); 9 | } 10 | 11 | public AppException(final String message, final Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public AppException(final Throwable cause) { 16 | super(cause); 17 | } 18 | 19 | public AppException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) { 20 | super(message, cause, enableSuppression, writableStackTrace); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /buildkit-parallelization/src/main/java/org/example/exception/ConfigAppException.java: -------------------------------------------------------------------------------- 1 | package org.example.exception; 2 | 3 | public class ConfigAppException extends AppException { 4 | public ConfigAppException() { 5 | } 6 | 7 | public ConfigAppException(final String message) { 8 | super(message); 9 | } 10 | 11 | public ConfigAppException(final String message, final Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public ConfigAppException(final Throwable cause) { 16 | super(cause); 17 | } 18 | 19 | public ConfigAppException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) { 20 | super(message, cause, enableSuppression, writableStackTrace); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /buildkit-parallelization/src/main/java/org/example/service/SignService.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apache.commons.codec.binary.Hex; 5 | import org.example.exception.ConfigAppException; 6 | 7 | import javax.crypto.Mac; 8 | import javax.crypto.spec.SecretKeySpec; 9 | import java.util.Arrays; 10 | import java.util.Optional; 11 | 12 | public class SignService { 13 | public static final String APP_SIGN_ALGO = "APP_SIGN_ALGO"; 14 | public static final String APP_SIGN_KEY = "APP_SIGN_KEY"; 15 | private final String algo; 16 | private final byte[] key; 17 | 18 | public SignService() { 19 | algo = Optional.ofNullable(System.getenv(APP_SIGN_ALGO)) 20 | .orElseThrow(() -> new ConfigAppException("No algorithm provided")); 21 | 22 | key = Optional.ofNullable(System.getenv(APP_SIGN_KEY)) 23 | .map(this::hexDecode) 24 | .orElseThrow(() -> new ConfigAppException("No key provided")); 25 | } 26 | 27 | public SignService(final String algo, final byte[] key) { 28 | this.algo = algo; 29 | this.key = Arrays.copyOf(key, key.length); 30 | } 31 | 32 | @SneakyThrows 33 | public String sign(final byte[] data) { 34 | final SecretKeySpec secretKeySpec = new SecretKeySpec(key, algo); 35 | final Mac mac = Mac.getInstance(algo); 36 | mac.init(secretKeySpec); 37 | mac.update(data); 38 | final byte[] signature = mac.doFinal(); 39 | return Hex.encodeHexString(signature); 40 | } 41 | 42 | @SneakyThrows 43 | private byte[] hexDecode(final String string) { 44 | return Hex.decodeHex(string); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /buildkit-parallelization/src/main/java/org/example/servlet/SignServlet.java: -------------------------------------------------------------------------------- 1 | package org.example.servlet; 2 | 3 | import jakarta.servlet.ServletException; 4 | import jakarta.servlet.annotation.WebServlet; 5 | import jakarta.servlet.http.HttpServlet; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import jakarta.servlet.http.HttpServletResponse; 8 | import org.example.service.SignService; 9 | 10 | import java.io.IOException; 11 | 12 | public class SignServlet extends HttpServlet { 13 | private static final long serialVersionUID = -7589549632947183818L; 14 | private final transient SignService service = new SignService(); 15 | 16 | @Override 17 | protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { 18 | final byte[] bytes = req.getInputStream().readAllBytes(); 19 | final String sign = service.sign(bytes); 20 | resp.getWriter().write(sign); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /buildkit-parallelization/src/test/java/org/example/service/SignServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.example.service; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apache.commons.codec.binary.Hex; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.nio.charset.StandardCharsets; 9 | 10 | class SignServiceTest { 11 | @SneakyThrows 12 | @Test 13 | void shouldSign() { 14 | final String algo = "HmacSHA256"; 15 | final String key = "073b9ce8277c4d70dba2edf33e80f6a2bde0dbd7e5c9d149ccc20ba8b125129b"; 16 | final byte[] data = "secret".getBytes(StandardCharsets.UTF_8); 17 | final String expected = "b4a5151c4d6939f8df7febe32ac94c3ce0f61112cbe1d8df0e51d7f2c4d9618b"; 18 | 19 | final SignService service = new SignService(algo, Hex.decodeHex(key)); 20 | 21 | final String actual = service.sign(data); 22 | 23 | Assertions.assertEquals(expected, actual); 24 | } 25 | } -------------------------------------------------------------------------------- /db-for-testers/.gitignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /db-for-testers/README.md: -------------------------------------------------------------------------------- 1 | Теги: `Docker` `PostgreSQL` 2 | 3 | # DB for Testers 4 | 5 | ## Задача 6 | 7 | Наши тестировщики прониклись идеями контейнеризации и активно начинают использовать Docker в своих тестах (ручных, автоматизированных и т.д.) 8 | 9 | Им очень нравится идея, что они могут просто взять контейнер PostgreSQL, закинуть туда БД и запускать тестируемые приложения с нужными данными 10 | 11 | Но вот проблема, они работают следующим образом: 12 | 1. Поднимают чистый контейнер 13 | 2. Инициализируют его данными (закидывая в `docker-entrypoint-initdb.d` конечно же) 14 | 3. Запускают серию тестов 15 | 16 | После этого им снова нужен чистый контейнер с теми же данными (т.е. приходится повторять шаги 1-2) 17 | 18 | Иногда эти шаги достаточно длительные — см. пример в `docker-entrypoint-initdb.d` 19 | 20 | Поэтому они очень просят сделать им образ уже сразу с инициализированным PostgreSQL, где в базе уже будут готовые данные (мы, конечно, понимаем, что это можно сделать и через `docker commit`, но давайте попробуем обойтись `docker build`, раз уж наш Навыкум про сборку контейнеров) 21 | 22 | Название базы и её владельца (и пароль, конечно же) нужно передавать через аргументы при сборке образа 23 | 24 |
25 | Спойлеры 26 | 27 | Для решения этой задачи настоятельно рекомендуем ознакомиться с docker-library/postgres#496 28 |
29 | 30 | ### Что нужно сделать 31 | 32 | Соберите образ с инициализированной БД, используя данные из `docker-entrypoint-initdb.d` (для этого, конечно же, нужно написать `Dockerfile`) 33 | 34 | За основу возьмите образ 15.4-alpine3.18 35 | 36 | При сборке через аргументы передавайте следующие данные: 37 | 1. Имя БД — `db` (`ARG DBNAME`) 38 | 2. Имя пользователя — `app` (`ARG DBUSER`) 39 | 3. Пароль пользователя — `pass` (`ARG DBPASS`) 40 | 41 | Никаких доп.настроек не нужно 42 | 43 | В собранном образе должен запускаться PostgreSQL на порту 5432 с инициализированной базой 44 | 45 | ## Реализация 46 | 47 | В качестве реализации CI/CD пайплайна и Docker Registry можно использовать любые, например (из облачных и бесплатных), [GitHub Actions](https://docs.github.com/en/actions) и [GitHub Packages](https://docs.github.com/packages) 48 | -------------------------------------------------------------------------------- /db-for-testers/docker-entrypoint-initdb.d/data.sql: -------------------------------------------------------------------------------- 1 | -- just emulate hard work 2 | SELECT clock_timestamp(), pg_sleep(30), clock_timestamp(); 3 | 4 | INSERT INTO "users"("login", "password", "roles") 5 | VALUES 6 | ('root', '{argon2}$argon2id$v=19$m=4096,t=3,p=1$VD2gbE9s9SvxSU3QnmeO9w$hosiwDgCWLdyZ6xrysnDg9fDE38frM65jxOj58ZkCXs', '{ROOT}'), 7 | ('admin', '{argon2}$argon2id$v=19$m=4096,t=3,p=1$VD2gbE9s9SvxSU3QnmeO9w$hosiwDgCWLdyZ6xrysnDg9fDE38frM65jxOj58ZkCXs', '{ADMIN}'), 8 | ('user', '{argon2}$argon2id$v=19$m=4096,t=3,p=1$VD2gbE9s9SvxSU3QnmeO9w$hosiwDgCWLdyZ6xrysnDg9fDE38frM65jxOj58ZkCXs', DEFAULT) 9 | RETURNING * 10 | ; -------------------------------------------------------------------------------- /db-for-testers/docker-entrypoint-initdb.d/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "users" 2 | ( 3 | "id" BIGSERIAL PRIMARY KEY, 4 | "login" TEXT NOT NULL UNIQUE, 5 | "password" TEXT NOT NULL, 6 | "roles" TEXT[] NOT NULL DEFAULT '{USER}', 7 | "created" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP 8 | ); -------------------------------------------------------------------------------- /graalvm-native-image-static/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | dev-*.yml 3 | HELP.md 4 | target/ 5 | !.mvn/wrapper/maven-wrapper.jar 6 | !**/src/main/**/target/ 7 | !**/src/test/**/target/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | 18 | ### IntelliJ IDEA ### 19 | .idea 20 | *.iws 21 | *.iml 22 | *.ipr 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | -------------------------------------------------------------------------------- /graalvm-native-image-static/README.md: -------------------------------------------------------------------------------- 1 | Теги: `Docker` `Java` `Maven` `GraalVM` `Native` 2 | 3 | # GraalVM Native Image (Static) 4 | 5 | ## Задача 6 | 7 | Есть проект на чистой Java 17, система сборки — Maven, разработчики хотят попробовать [GraalVM Native Image](https://www.graalvm.org/jdk17/reference-manual/native-image/) 8 | 9 | Для Maven есть [Maven Native Plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html), который и позволит скомпилировать приложение в бинарный исполняемый файл формата ELF 10 | 11 | Разработчики хотят сделать так, чтобы выбор между сборкой "обычного приложения" и Native Image осуществлялся через [профили Maven](https://maven.apache.org/guides/introduction/introduction-to-profiles.html), определяемые в самом проекте 12 | 13 | По умолчанию, активным должен быть профиль, который собирает "обычное приложение" (не Native Image) 14 | 15 | ### Сборка 16 | 17 | Сборка проходит стандартным образом, через `mvn package`, никаких внешних зависимостей у проекта (в виде подключаемых при исполнении библиотек нет) 18 | 19 | ### API 20 | 21 | Сервер запускается на порту, указанном через параметр `port` или переменную окружения `PORT` и реагирует на команду `UUID`, выдавая в ответ случайный [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) 22 | 23 | Как проверить: 24 | 1. Подключаемся с помощью `nc` (`netcat`) по нужному порту 25 | 2. Вводим `UUID⏎`, (где `⏎` — Enter для отправки данных) 26 | 3. Получаем в ответ: `b022e6b9-957a-4f22-b519-2fb57ca76caf` (пример) 27 | 28 |
29 | Спойлеры: пример вызова nc для тестирования 30 | 31 | ```shell 32 | $ nc -u localhost 9999 33 | UUID 34 | b022e6b9-957a-4f22-b519-2fb57ca76caf 35 | ``` 36 | 37 |
38 | 39 | ### Что нужно сделать 40 | 41 | 1. Собрать всё с помощью Maven и Native Image (параллелить ничего не нужно, т.к. в приложении нет авто-тестов, проверок стиля кода и т.д.) 42 | 2. Упаковать всё в [`Scratch`](https://hub.docker.com/_/scratch) 43 | 3. Запускать приложение не от root 44 | 45 | ## Реализация 46 | 47 | В качестве реализации CI/CD пайплайна и Docker Registry можно использовать любые, например (из облачных и бесплатных), [GitHub Actions](https://docs.github.com/en/actions) и [GitHub Packages](https://docs.github.com/packages) 48 | 49 | ## Полезные ссылки 50 | 51 | 1. [GraalVM Native Image](https://www.graalvm.org/jdk17/reference-manual/native-image/) 52 | 2. [Netcat MAN](https://linux.die.net/man/1/nc) 53 | -------------------------------------------------------------------------------- /graalvm-native-image-static/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | native 9 | 1.0.0 10 | 11 | 12 | 17 13 | 17 14 | UTF-8 15 | 1.18.30 16 | 17 | 18 | 19 | 20 | org.projectlombok 21 | lombok 22 | ${lombok.version} 23 | provided 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /graalvm-native-image-static/src/main/java/org/example/Main.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import org.example.server.Server; 4 | 5 | import java.io.IOException; 6 | import java.util.Optional; 7 | 8 | public class Main { 9 | public static void main(String[] args) throws IOException { 10 | final Server server = new Server(); 11 | final String port = Optional.ofNullable(System.getProperty("port")) 12 | .orElse(System.getenv("PORT")) 13 | ; 14 | server.serve(Integer.parseInt(port)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /graalvm-native-image-static/src/main/java/org/example/server/Server.java: -------------------------------------------------------------------------------- 1 | package org.example.server; 2 | 3 | import lombok.extern.java.Log; 4 | 5 | import java.io.IOException; 6 | import java.net.DatagramPacket; 7 | import java.net.DatagramSocket; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.UUID; 10 | import java.util.logging.Level; 11 | 12 | @Log 13 | public class Server { 14 | public void serve(final int port) throws IOException { 15 | try ( 16 | final DatagramSocket datagramSocket = new DatagramSocket(port); 17 | ) { 18 | log.log(Level.INFO, "server created, listen on: {0}", port); 19 | 20 | while (true) { 21 | final byte[] buffer = new byte[1024]; 22 | final DatagramPacket in = new DatagramPacket(buffer, buffer.length); 23 | datagramSocket.receive(in); 24 | log.log(Level.INFO, "received packet from {0}:{1}", new Object[]{in.getAddress(), in.getPort()}); 25 | final String incomingMessage = new String(in.getData(), in.getOffset(), in.getLength(), StandardCharsets.UTF_8).trim(); 26 | log.log(Level.FINE, "message: {0}", incomingMessage); 27 | 28 | switch (incomingMessage) { 29 | case "UUID": { 30 | final byte[] replyMessage = String.format("%s%n", UUID.randomUUID()).getBytes(StandardCharsets.UTF_8); 31 | final DatagramPacket out = new DatagramPacket(replyMessage, replyMessage.length, in.getAddress(), in.getPort()); 32 | datagramSocket.send(out); 33 | break; 34 | } 35 | default: { 36 | final byte[] replyMessage = String.format("Invalid Command: %s%n", incomingMessage).getBytes(StandardCharsets.UTF_8); 37 | final DatagramPacket out = new DatagramPacket(replyMessage, replyMessage.length, in.getAddress(), in.getPort()); 38 | datagramSocket.send(out); 39 | break; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /gradle-application/.gitignore: -------------------------------------------------------------------------------- 1 | /tomcat* 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### IntelliJ IDEA ### 9 | .idea/ 10 | *.iws 11 | *.iml 12 | *.ipr 13 | out/ 14 | !**/src/main/**/out/ 15 | !**/src/test/**/out/ 16 | 17 | ### Eclipse ### 18 | .apt_generated 19 | .classpath 20 | .factorypath 21 | .project 22 | .settings 23 | .springBeans 24 | .sts4-cache 25 | bin/ 26 | !**/src/main/**/bin/ 27 | !**/src/test/**/bin/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### Mac OS ### 40 | .DS_Store -------------------------------------------------------------------------------- /gradle-application/README.md: -------------------------------------------------------------------------------- 1 | Теги: `Docker` `Java` `Gradle` 2 | 3 | # Gradle Application 4 | 5 | ## Задача 6 | 7 | Разработчики проекта совсем не дружат с Docker, контейнерами и "всей этой вашей новомодной \[подставьте наименование по желанию\]", поэтому нам выпала задача упаковать это всё в образ 8 | 9 | Что имеем: проект на Java 17 с системой сборки Gradle 10 | 11 | Используемый порт: `9999` 12 | 13 | Команда для тестирования: 14 | ```shell 15 | curl -X GET http://localhost:9999 16 | ``` 17 | 18 | Ожидаемый ответ: 19 | ```shell 20 | {"uuid": "рандомный UUID"} 21 | ``` 22 | 23 | Также есть Healthcheck: 24 | ```shell 25 | curl -X GET http://localhost:9999/health 26 | ``` 27 | 28 | Ожидаемый ответ: 29 | ```shell 30 | {"status": "UP"} 31 | ``` 32 | 33 | Приоритеты по использованию базового образа: 34 | 1. Scratch 35 | 2. Distroless (не `debug`) 36 | 3. Eclipse Temurin 37 | 38 | Запуск не от `root`'а 39 | 40 |
41 | Спойлеры 42 | 43 | Вполне возможно, что первые два варианта использовать нецелесообразно по техническим соображениям: подготовьте обоснованный ответ на вопрос "почему" 44 |
45 | 46 | 47 | ## Инструкции по сборке проекта 48 | 49 | Есть плагин [`application`](https://docs.gradle.org/current/userguide/application_plugin.html), который умеет собирать дистрибутив проекта 50 | 51 | Делается это с помощью следующей команды: 52 | 53 | ```shell 54 | ./gradlew assemble 55 | ``` 56 | 57 | Файл `gradle-application-1.0.0.tar` и будет содержать дистрибутив для развёртывания 58 | 59 | ## Реализация 60 | 61 | В качестве реализации CI/CD пайплайна и Docker Registry можно использовать любые, например (из облачных и бесплатных), [GitHub Actions](https://docs.github.com/en/actions) и [GitHub Packages](https://docs.github.com/packages) 62 | 63 | -------------------------------------------------------------------------------- /gradle-application/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'application' 4 | } 5 | 6 | group = 'org.example' 7 | version = '1.0.0' 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | // https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core 15 | implementation 'org.apache.tomcat.embed:tomcat-embed-core:10.1.15' 16 | // https://mvnrepository.com/artifact/com.google.code.gson/gson 17 | implementation 'com.google.code.gson:gson:2.10.1' 18 | } 19 | -------------------------------------------------------------------------------- /gradle-application/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slurmio/devopscases/09b9f14317068252642b162b73b30279e20a2925/gradle-application/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle-application/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 24 09:08:01 MSK 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradle-application/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradle-application/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /gradle-application/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'gradle-application' 2 | 3 | -------------------------------------------------------------------------------- /gradle-application/src/main/java/org/example/Main.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import com.google.gson.Gson; 4 | import org.apache.catalina.Context; 5 | import org.apache.catalina.LifecycleException; 6 | import org.apache.catalina.connector.Connector; 7 | import org.apache.catalina.startup.Tomcat; 8 | import org.example.serlvet.HealthServlet; 9 | import org.example.serlvet.MainServlet; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | 16 | public class Main { 17 | public static void main(String[] args) throws IOException, LifecycleException { 18 | final int port = 9999; 19 | final Tomcat tomcat = new Tomcat(); 20 | tomcat.setPort(port); 21 | 22 | final Connector connector = new Connector("HTTP/1.1"); 23 | connector.setPort(port); 24 | tomcat.setConnector(connector); 25 | 26 | 27 | final Path baseDir = Files.createTempDirectory("tomcat-"); 28 | tomcat.setBaseDir(baseDir.toFile().getAbsolutePath()); 29 | 30 | final Context ctx = tomcat.addContext("", new File(".").getAbsolutePath()); 31 | 32 | final Gson gson = new Gson(); 33 | final MainServlet mainServlet = new MainServlet(gson); 34 | 35 | Tomcat.addServlet(ctx, "main", mainServlet); 36 | ctx.addServletMappingDecoded("/*", "main"); 37 | 38 | final HealthServlet healthServlet = new HealthServlet(gson); 39 | Tomcat.addServlet(ctx, "health", healthServlet); 40 | ctx.addServletMappingDecoded("/health", "health"); 41 | 42 | tomcat.start(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gradle-application/src/main/java/org/example/dto/HealthResponseDto.java: -------------------------------------------------------------------------------- 1 | package org.example.dto; 2 | 3 | public record HealthResponseDto(Status status) { 4 | public enum Status { 5 | UP, DOWN 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /gradle-application/src/main/java/org/example/dto/MainResponseDto.java: -------------------------------------------------------------------------------- 1 | package org.example.dto; 2 | 3 | import java.util.UUID; 4 | 5 | public record MainResponseDto(UUID uuid) { 6 | } 7 | -------------------------------------------------------------------------------- /gradle-application/src/main/java/org/example/serlvet/HealthServlet.java: -------------------------------------------------------------------------------- 1 | package org.example.serlvet; 2 | 3 | import com.google.gson.Gson; 4 | import jakarta.servlet.http.HttpServlet; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import jakarta.servlet.http.HttpServletResponse; 7 | import org.example.dto.HealthResponseDto; 8 | 9 | import java.io.IOException; 10 | 11 | public class HealthServlet extends HttpServlet { 12 | private final transient Gson gson; 13 | 14 | public HealthServlet(final Gson gson) { 15 | this.gson = gson; 16 | } 17 | 18 | @Override 19 | protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws IOException { 20 | final HealthResponseDto dto = new HealthResponseDto(HealthResponseDto.Status.UP); 21 | resp.setHeader("Content-Type", "application/json"); 22 | this.gson.toJson(dto, resp.getWriter()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /gradle-application/src/main/java/org/example/serlvet/MainServlet.java: -------------------------------------------------------------------------------- 1 | package org.example.serlvet; 2 | 3 | import com.google.gson.Gson; 4 | import jakarta.servlet.ServletException; 5 | import jakarta.servlet.http.HttpServlet; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import jakarta.servlet.http.HttpServletResponse; 8 | import org.example.dto.MainResponseDto; 9 | 10 | import java.io.IOException; 11 | import java.time.Instant; 12 | import java.util.UUID; 13 | 14 | public class MainServlet extends HttpServlet { 15 | private final transient Gson gson; 16 | 17 | public MainServlet(final Gson gson) { 18 | this.gson = gson; 19 | } 20 | 21 | @Override 22 | protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws IOException { 23 | final MainResponseDto dto = new MainResponseDto(UUID.randomUUID()); 24 | resp.setHeader("Content-Type", "application/json"); 25 | this.gson.toJson(dto, resp.getWriter()); 26 | } 27 | } 28 | --------------------------------------------------------------------------------