mirror of
https://github.com/zitadel/zitadel.git
synced 2025-10-28 07:06:25 +00:00
feat: Merge master (#1260)
* chore(site): dependabot deps (#1148) * chore(deps): bump highlight.js from 10.4.1 to 10.5.0 in /site (#1143) Bumps [highlight.js](https://github.com/highlightjs/highlight.js) from 10.4.1 to 10.5.0. - [Release notes](https://github.com/highlightjs/highlight.js/releases) - [Changelog](https://github.com/highlightjs/highlight.js/blob/master/CHANGES.md) - [Commits](https://github.com/highlightjs/highlight.js/compare/10.4.1...10.5.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @babel/plugin-transform-runtime in /site (#1144) Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.12.1 to 7.12.10. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.12.10/packages/babel-plugin-transform-runtime) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump sirv from 1.0.7 to 1.0.10 in /site (#1145) Bumps [sirv](https://github.com/lukeed/sirv) from 1.0.7 to 1.0.10. - [Release notes](https://github.com/lukeed/sirv/releases) - [Commits](https://github.com/lukeed/sirv/compare/v1.0.7...v1.0.10) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump rollup from 2.34.0 to 2.35.1 in /site (#1142) Bumps [rollup](https://github.com/rollup/rollup) from 2.34.0 to 2.35.1. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.34.0...v2.35.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @rollup/plugin-node-resolve in /site (#1141) Bumps [@rollup/plugin-node-resolve](https://github.com/rollup/plugins) from 10.0.0 to 11.0.1. - [Release notes](https://github.com/rollup/plugins/releases) - [Commits](https://github.com/rollup/plugins/compare/node-resolve-v10.0.0...commonjs-v11.0.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump marked from 1.2.5 to 1.2.7 in /site (#1140) Bumps [marked](https://github.com/markedjs/marked) from 1.2.5 to 1.2.7. - [Release notes](https://github.com/markedjs/marked/releases) - [Changelog](https://github.com/markedjs/marked/blob/master/release.config.js) - [Commits](https://github.com/markedjs/marked/compare/v1.2.5...v1.2.7) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @babel/core from 7.12.9 to 7.12.10 in /site (#1139) Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.12.9 to 7.12.10. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.12.10/packages/babel-core) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump rollup-plugin-svelte from 6.1.1 to 7.0.0 in /site (#1138) Bumps [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) from 6.1.1 to 7.0.0. - [Release notes](https://github.com/sveltejs/rollup-plugin-svelte/releases) - [Changelog](https://github.com/sveltejs/rollup-plugin-svelte/blob/master/CHANGELOG.md) - [Commits](https://github.com/sveltejs/rollup-plugin-svelte/compare/v6.1.1...v7.0.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @babel/preset-env from 7.12.1 to 7.12.11 in /site (#1137) Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.12.1 to 7.12.11. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.12.11/packages/babel-preset-env) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * downgrade svelte plugin Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(console): dependabot deps (#1147) * chore(deps-dev): bump @types/node from 14.14.13 to 14.14.19 in /console (#1146) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.13 to 14.14.19. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump ts-protoc-gen from 0.13.0 to 0.14.0 in /console (#1129) Bumps [ts-protoc-gen](https://github.com/improbable-eng/ts-protoc-gen) from 0.13.0 to 0.14.0. - [Release notes](https://github.com/improbable-eng/ts-protoc-gen/releases) - [Changelog](https://github.com/improbable-eng/ts-protoc-gen/blob/master/CHANGELOG.md) - [Commits](https://github.com/improbable-eng/ts-protoc-gen/compare/0.13.0...0.14.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular/language-service in /console (#1128) Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 11.0.4 to 11.0.5. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/11.0.5/packages/language-service) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular/cli from 11.0.4 to 11.0.5 in /console (#1127) Bumps [@angular/cli](https://github.com/angular/angular-cli) from 11.0.4 to 11.0.5. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/compare/v11.0.4...v11.0.5) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular-devkit/build-angular in /console (#1126) Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 0.1100.4 to 0.1100.5. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/commits) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> * audit Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat: e-mail templates (#1158) * View definition added * Get templates and texts from the database. * Fill in texts in templates * Fill in texts in templates * Client API added * Weekly backup * Weekly backup * Daily backup * Weekly backup * Tests added * Corrections from merge branch * Fixes from pull request review * chore(console): dependencies (#1189) * chore(deps-dev): bump @angular/language-service in /console (#1187) Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 11.0.5 to 11.0.9. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/11.0.9/packages/language-service) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump google-proto-files from 2.3.0 to 2.4.0 in /console (#1186) Bumps [google-proto-files](https://github.com/googleapis/nodejs-proto-files) from 2.3.0 to 2.4.0. - [Release notes](https://github.com/googleapis/nodejs-proto-files/releases) - [Changelog](https://github.com/googleapis/nodejs-proto-files/blob/master/CHANGELOG.md) - [Commits](https://github.com/googleapis/nodejs-proto-files/compare/v2.3.0...v2.4.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @types/node from 14.14.19 to 14.14.21 in /console (#1185) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.19 to 14.14.21. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular/cli from 11.0.5 to 11.0.7 in /console (#1184) Bumps [@angular/cli](https://github.com/angular/angular-cli) from 11.0.5 to 11.0.7. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/compare/v11.0.5...v11.0.7) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump karma from 5.2.3 to 6.0.0 in /console (#1183) Bumps [karma](https://github.com/karma-runner/karma) from 5.2.3 to 6.0.0. - [Release notes](https://github.com/karma-runner/karma/releases) - [Changelog](https://github.com/karma-runner/karma/blob/master/CHANGELOG.md) - [Commits](https://github.com/karma-runner/karma/compare/v5.2.3...v6.0.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular-devkit/build-angular in /console (#1182) Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 0.1100.5 to 0.1100.7. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/commits) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(console): trigger unauthenticated dialog only once (#1170) * fix: trigger dialog once * remove log * typed trigger * chore(console): dependencies (#1205) * chore(deps-dev): bump stylelint from 13.8.0 to 13.9.0 in /console (#1204) Bumps [stylelint](https://github.com/stylelint/stylelint) from 13.8.0 to 13.9.0. - [Release notes](https://github.com/stylelint/stylelint/releases) - [Changelog](https://github.com/stylelint/stylelint/blob/master/CHANGELOG.md) - [Commits](https://github.com/stylelint/stylelint/compare/13.8.0...13.9.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular/language-service in /console (#1203) Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 11.0.9 to 11.1.0. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/11.1.0/packages/language-service) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump karma from 6.0.0 to 6.0.1 in /console (#1202) Bumps [karma](https://github.com/karma-runner/karma) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/karma-runner/karma/releases) - [Changelog](https://github.com/karma-runner/karma/blob/master/CHANGELOG.md) - [Commits](https://github.com/karma-runner/karma/compare/v6.0.0...v6.0.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular/cli from 11.0.7 to 11.1.1 in /console (#1201) Bumps [@angular/cli](https://github.com/angular/angular-cli) from 11.0.7 to 11.1.1. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/compare/v11.0.7...v11.1.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @types/jasmine from 3.6.2 to 3.6.3 in /console (#1200) Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 3.6.2 to 3.6.3. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> * chore(deps-dev): bump @types/node from 14.14.21 to 14.14.22 in /console (#1199) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.21 to 14.14.22. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular-devkit/build-angular in /console (#1198) Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 0.1100.7 to 0.1101.1. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/commits) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> * chore(deps): bump angularx-qrcode from 10.0.11 to 11.0.0 in /console (#1197) Bumps [angularx-qrcode](https://github.com/cordobo/angularx-qrcode) from 10.0.11 to 11.0.0. - [Release notes](https://github.com/cordobo/angularx-qrcode/releases) - [Commits](https://github.com/cordobo/angularx-qrcode/compare/10.0.11...11.0.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix pack lock Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: handle sequence correctly in subscription (#1209) * fix: correct master after merges again (#1230) * chore(docs): correct `iss` claim of jwt profile (#1229) * core(docs): correct `iss` claim of jwt profile * fix: correct master after merges again (#1230) * feat(login): new palette based styles (#1149) * chore(deps-dev): bump rollup from 2.33.2 to 2.34.0 in /site (#1040) Bumps [rollup](https://github.com/rollup/rollup) from 2.33.2 to 2.34.0. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.33.2...v2.34.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump svelte-i18n from 3.2.5 to 3.3.0 in /site (#1039) Bumps [svelte-i18n](https://github.com/kaisermann/svelte-i18n) from 3.2.5 to 3.3.0. - [Release notes](https://github.com/kaisermann/svelte-i18n/releases) - [Changelog](https://github.com/kaisermann/svelte-i18n/blob/main/CHANGELOG.md) - [Commits](https://github.com/kaisermann/svelte-i18n/compare/v3.2.5...v3.3.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @rollup/plugin-url from 5.0.1 to 6.0.0 in /site (#1038) Bumps [@rollup/plugin-url](https://github.com/rollup/plugins) from 5.0.1 to 6.0.0. - [Release notes](https://github.com/rollup/plugins/releases) - [Commits](https://github.com/rollup/plugins/compare/url-v5.0.1...url-v6.0.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump svelte from 3.29.7 to 3.30.1 in /site (#1037) Bumps [svelte](https://github.com/sveltejs/svelte) from 3.29.7 to 3.30.1. - [Release notes](https://github.com/sveltejs/svelte/releases) - [Changelog](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md) - [Commits](https://github.com/sveltejs/svelte/compare/v3.29.7...v3.30.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump marked from 1.2.4 to 1.2.5 in /site (#1036) Bumps [marked](https://github.com/markedjs/marked) from 1.2.4 to 1.2.5. - [Release notes](https://github.com/markedjs/marked/releases) - [Changelog](https://github.com/markedjs/marked/blob/master/release.config.js) - [Commits](https://github.com/markedjs/marked/compare/v1.2.4...v1.2.5) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @babel/core from 7.12.3 to 7.12.9 in /site (#1035) Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.12.3 to 7.12.9. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.12.9/packages/babel-core) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump rollup-plugin-svelte from 6.1.1 to 7.0.0 in /site (#1034) Bumps [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) from 6.1.1 to 7.0.0. - [Release notes](https://github.com/sveltejs/rollup-plugin-svelte/releases) - [Changelog](https://github.com/sveltejs/rollup-plugin-svelte/blob/master/CHANGELOG.md) - [Commits](https://github.com/sveltejs/rollup-plugin-svelte/compare/v6.1.1...v7.0.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @rollup/plugin-commonjs in /site (#1033) Bumps [@rollup/plugin-commonjs](https://github.com/rollup/plugins) from 15.1.0 to 17.0.0. - [Release notes](https://github.com/rollup/plugins/releases) - [Commits](https://github.com/rollup/plugins/compare/commonjs-v15.1.0...commonjs-v17.0.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @rollup/plugin-node-resolve in /site (#1032) Bumps [@rollup/plugin-node-resolve](https://github.com/rollup/plugins) from 10.0.0 to 11.0.0. - [Release notes](https://github.com/rollup/plugins/releases) - [Commits](https://github.com/rollup/plugins/compare/node-resolve-v10.0.0...commonjs-v11.0.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @babel/preset-env from 7.12.1 to 7.12.7 in /site (#1031) Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.12.1 to 7.12.7. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.12.7/packages/babel-preset-env) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * go * bundle files, lgn-color, legacy theme * remove old references * light dark context, button styles, zitadel brand * button theme, edit templates * typography theme mixins * input styles, container, extend light dark palette * footer, palette, container * container, label, assets, header * action container, input, typography label, adapt button theme * a and footer styles, adapt palette * user log profile, resourcetempurl * postinstall againnn * wrochage * rm local grpc * button elevation, helper for components * radio * radio button mixins, bundle * qr code styles, secret clipboard, icon pack * stroked buttons, icon buttons, header action, typography * fix password policy styles * account selection * account selection, lgn avatar * mocks * template fixes, animations scss * checkbox, register temp * checkbox appr * fix checkbox, remove input interference * select theme * avatar script, user selection, password policy validation fix * fix formfield state for register and change pwd * footer, main style, qr code fix, mfa type fix, account sel, checkbox * fotter tos, user select * reverse buttons for intial submit action * theme script, themed error messages, header img source * content wrapper, i18n, mobile * emptyline * idp mixins, fix unstyled html * register container * register layout, list themes, policy theme, register org * massive asset cleanup * fix source path, add missing icon, fix complexity refs, prefix * remove material icons, unused assets, fix icon font * move icon pack * avatar, contrast theme, error fix * zitadel css map * revert go mod * fix mfa verify actions * add idp styles * fix google colors, idp styles * fix: bugs * fix register options, google * fix script, mobile layout * precompile font selection * go mod tidy * assets and cleanup * input suffix, fix alignment, actions, add progress bar themes * progress bar mixins, layout fixes * remove test from loginname * cleanup comments, scripts * clear comments * fix external back button * fix mfa alignment * fix actions layout, on dom change listener for suffix * free tier change, success label * fix: button font line-height * remove tabindex * remove comment * remove comment * Update internal/ui/login/handler/password_handler.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Maximilian Peintner <csaq7175@uibk.ac.at> Co-authored-by: Livio Amstutz <livio.a@gmail.com> * chore(console): dependencies (#1233) * chore(deps-dev): bump @angular-devkit/build-angular in /console (#1214) Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 0.1101.1 to 0.1101.2. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/commits) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump karma from 6.0.1 to 6.0.3 in /console (#1215) Bumps [karma](https://github.com/karma-runner/karma) from 6.0.1 to 6.0.3. - [Release notes](https://github.com/karma-runner/karma/releases) - [Changelog](https://github.com/karma-runner/karma/blob/master/CHANGELOG.md) - [Commits](https://github.com/karma-runner/karma/compare/v6.0.1...v6.0.3) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular/language-service in /console (#1216) Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 11.1.0 to 11.1.1. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/11.1.1/packages/language-service) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular/cli from 11.1.1 to 11.1.2 in /console (#1217) Bumps [@angular/cli](https://github.com/angular/angular-cli) from 11.1.1 to 11.1.2. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/compare/v11.1.1...v11.1.2) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> * lock * site deps Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: get email texts with default language (#1238) * fix(login): mail verification (#1237) * fix: mail verification * not block, stroked * fix: issues of new login ui (#1241) * fix: i18n of register * fix: autofocus * feat(operator): zitadel and database operator (#1208) * feat(operator): add base for zitadel operator * fix(operator): changed pipeline to release operator * fix(operator): fmt with only one parameter * fix(operator): corrected workflow job name * fix(zitadelctl): added restore and backuplist command * fix(zitadelctl): scale for restore * chore(container): use scratch for deploy container * fix(zitadelctl): limit image to scratch * fix(migration): added migration scripts for newer version * fix(operator): changed handling of kubeconfig in operator logic * fix(operator): changed handling of secrets in operator logic * fix(operator): use new version of zitadel * fix(operator): added path for migrations * fix(operator): delete doublets of migration scripts * fix(operator): delete subpaths and integrate logic into init container * fix(operator): corrected path in dockerfile for local migrations * fix(operator): added migrations for cockroachdb-secure * fix(operator): delete logic for ambassador module * fix(operator): added read and write secret commands * fix(operator): correct and align operator pipeline with zitadel pipeline * fix(operator): correct yaml error in operator pipeline * fix(operator): correct action name in operator pipeline * fix(operator): correct case-sensitive filename in operator pipeline * fix(operator): upload artifacts from buildx output * fix(operator): corrected attribute spelling error * fix(operator): combined jobs for operator binary and image * fix(operator): added missing comma in operator pipeline * fix(operator): added codecov for operator image * fix(operator): added codecov for operator image * fix(testing): code changes for testing and several unit-tests (#1009) * fix(operator): usage of interface of kubernetes client for testing and several unit-tests * fix(operator): several unit-tests * fix(operator): several unit-tests * fix(operator): changed order for the operator logic * fix(operator): added version of zitadelctl from semantic release * fix(operator): corrected function call with version of zitadelctl * fix(operator): corrected function call with version of zitadelctl * fix(operator): add check output to operator release pipeline * fix(operator): set --short length everywhere to 12 * fix(operator): zitadel setup in job instead of exec with several unit tests * fix(operator): fixes to combine newest zitadel and testing branch * fix(operator): corrected path in Dockerfile * fix(operator): fixed unit-test that was ignored during changes * fix(operator): fixed unit-test that was ignored during changes * fix(operator): corrected Dockerfile to correctly use env variable * fix(operator): quickfix takeoff deployment * fix(operator): corrected the clusterrolename in the applied artifacts * fix: update secure migrations * fix(operator): migrations (#1057) * fix(operator): copied migrations from orbos repository * fix(operator): newest migrations * chore: use cockroach-secure * fix: rename migration * fix: remove insecure cockroach migrations Co-authored-by: Stefan Benz <stefan@caos.ch> * fix: finalize labels * fix(operator): cli logging concurrent and fixe deployment of operator during restore * fix: finalize labels and cli commands * fix: restore * chore: cockroachdb is always secure * chore: use orbos consistent-labels latest commit * test: make tests compatible with new labels * fix: default to sa token for start command * fix: use cockroachdb v12.02 * fix: don't delete flyway user * test: fix migration test * fix: use correct table qualifiers * fix: don't alter sequence ownership * fix: upgrade flyway * fix: change ownership of all dbs and tables to admin user * fix: change defaultdb user * fix: treat clientid status codes >= 400 as errors * fix: reconcile specified ZITADEL version, not binary version * fix: add ca-certs * fix: use latest orbos code * fix: use orbos with fixed race condition * fix: use latest ORBOS code * fix: use latest ORBOS code * fix: make migration and scaling around restoring work * fix(operator): move zitadel operator * chore(migrations): include owner change migration * feat(db): add code base for database operator * fix(db): change used image registry for database operator * fix(db): generated mock * fix(db): add accidentally ignored file * fix(db): add cockroachdb backup image to pipeline * fix(db): correct pipeline and image versions * fix(db): correct version of used orbos * fix(db): correct database import * fix(db): go mod tidy * fix(db): use new version for orbos * fix(migrations): include migrations into zitadelctl binary (#1211) * fix(db): use statik to integrate migrations into binary * fix(migrations): corrections unit tests and pipeline for integrated migrations into zitadelctl binary * fix(migrations): correction in dockerfile for pipeline build * fix(migrations): correction in dockerfile for pipeline build * fix(migrations): dockerfile changes for cache optimization * fix(database): correct used part-of label in database operator * fix(database): correct used selectable label in zitadel operator * fix(operator): correct lables for user secrets in zitadel operator * fix(operator): correct lables for service test in zitadel operator * fix: don't enable database features for user operations (#1227) * fix: don't enable database features for user operations * fix: omit database feature for connection info adapter * fix: use latest orbos version * fix: update ORBOS (#1240) Co-authored-by: Florian Forster <florian@caos.ch> Co-authored-by: Elio Bischof <eliobischof@gmail.com> * Merge branch 'new-eventstore' into cascades # Conflicts: # internal/auth/repository/auth_request.go # internal/auth/repository/eventsourcing/eventstore/auth_request.go # internal/management/repository/eventsourcing/eventstore/user_grant.go # internal/management/repository/user_grant.go # internal/ui/login/handler/external_login_handler.go # internal/ui/login/handler/external_register_handler.go # internal/ui/login/handler/init_password_handler.go # internal/ui/login/handler/register_handler.go # internal/user/repository/view/model/notify_user.go # internal/v2/command/org_policy_login.go # internal/v2/command/project.go # internal/v2/command/user.go # internal/v2/command/user_human.go # internal/v2/command/user_human_externalidp.go # internal/v2/command/user_human_init.go # internal/v2/command/user_human_password.go # internal/v2/command/user_human_webauthn.go # internal/v2/domain/next_step.go # internal/v2/domain/policy_login.go # internal/v2/domain/request.go * chore: add local migrate_local.go again (#1261) Co-authored-by: Max Peintner <max@caos.ch> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Michael Waeger <49439088+michaelulrichwaeger@users.noreply.github.com> Co-authored-by: Livio Amstutz <livio.a@gmail.com> Co-authored-by: Maximilian Peintner <csaq7175@uibk.ac.at> Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com> Co-authored-by: Florian Forster <florian@caos.ch> Co-authored-by: Elio Bischof <eliobischof@gmail.com>
This commit is contained in:
64
operator/database/kinds/databases/core/current.go
Normal file
64
operator/database/kinds/databases/core/current.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator"
|
||||
)
|
||||
|
||||
const queriedName = "database"
|
||||
|
||||
type DatabaseCurrent interface {
|
||||
GetURL() string
|
||||
GetPort() string
|
||||
GetReadyQuery() operator.EnsureFunc
|
||||
GetCertificateKey() *rsa.PrivateKey
|
||||
SetCertificateKey(*rsa.PrivateKey)
|
||||
GetCertificate() []byte
|
||||
SetCertificate([]byte)
|
||||
GetAddUserFunc() func(user string) (operator.QueryFunc, error)
|
||||
GetDeleteUserFunc() func(user string) (operator.DestroyFunc, error)
|
||||
GetListUsersFunc() func(k8sClient kubernetes.ClientInt) ([]string, error)
|
||||
GetListDatabasesFunc() func(k8sClient kubernetes.ClientInt) ([]string, error)
|
||||
}
|
||||
|
||||
func ParseQueriedForDatabase(queried map[string]interface{}) (DatabaseCurrent, error) {
|
||||
queriedDB, ok := queried[queriedName]
|
||||
if !ok {
|
||||
return nil, errors.New("no current state for database found")
|
||||
}
|
||||
currentDBTree, ok := queriedDB.(*tree.Tree)
|
||||
if !ok {
|
||||
return nil, errors.New("current state does not fullfil interface")
|
||||
}
|
||||
currentDB, ok := currentDBTree.Parsed.(DatabaseCurrent)
|
||||
if !ok {
|
||||
return nil, errors.New("current state does not fullfil interface")
|
||||
}
|
||||
|
||||
return currentDB, nil
|
||||
}
|
||||
|
||||
func SetQueriedForDatabase(queried map[string]interface{}, databaseCurrent *tree.Tree) {
|
||||
queried[queriedName] = databaseCurrent
|
||||
}
|
||||
|
||||
func SetQueriedForDatabaseDBList(queried map[string]interface{}, databases []string) {
|
||||
currentDBList := &CurrentDBList{
|
||||
Common: &tree.Common{
|
||||
Kind: "DBList",
|
||||
Version: "V0",
|
||||
},
|
||||
Current: &DatabaseCurrentDBList{
|
||||
Databases: databases,
|
||||
},
|
||||
}
|
||||
|
||||
currentDB := &tree.Tree{
|
||||
Parsed: currentDBList,
|
||||
}
|
||||
|
||||
SetQueriedForDatabase(queried, currentDB)
|
||||
}
|
||||
65
operator/database/kinds/databases/core/dblist.go
Normal file
65
operator/database/kinds/databases/core/dblist.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator"
|
||||
)
|
||||
|
||||
var current DatabaseCurrent = &CurrentDBList{}
|
||||
|
||||
type CurrentDBList struct {
|
||||
Common *tree.Common `yaml:",inline"`
|
||||
Current *DatabaseCurrentDBList
|
||||
}
|
||||
|
||||
type DatabaseCurrentDBList struct {
|
||||
Databases []string
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) GetURL() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) GetPort() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) GetReadyQuery() operator.EnsureFunc {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) GetCertificateKey() *rsa.PrivateKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) SetCertificateKey(key *rsa.PrivateKey) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) GetCertificate() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) SetCertificate(cert []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) GetListDatabasesFunc() func(k8sClient kubernetes.ClientInt) ([]string, error) {
|
||||
return func(k8sClient kubernetes.ClientInt) ([]string, error) {
|
||||
return c.Current.Databases, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) GetListUsersFunc() func(k8sClient kubernetes.ClientInt) ([]string, error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) GetAddUserFunc() func(user string) (operator.QueryFunc, error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CurrentDBList) GetDeleteUserFunc() func(user string) (operator.DestroyFunc, error) {
|
||||
return nil
|
||||
}
|
||||
3
operator/database/kinds/databases/core/generate.go
Normal file
3
operator/database/kinds/databases/core/generate.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package core
|
||||
|
||||
//go:generate mockgen -source current.go -package coremock -destination mock/current.mock.go github.com/caos/internal/operator/database/kinds/databases/core DatabaseCurrent
|
||||
186
operator/database/kinds/databases/core/mock/current.mock.go
Normal file
186
operator/database/kinds/databases/core/mock/current.mock.go
Normal file
@@ -0,0 +1,186 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: current.go
|
||||
|
||||
// Package coremock is a generated GoMock package.
|
||||
package coremock
|
||||
|
||||
import (
|
||||
rsa "crypto/rsa"
|
||||
kubernetes "github.com/caos/orbos/pkg/kubernetes"
|
||||
operator "github.com/caos/zitadel/operator"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockDatabaseCurrent is a mock of DatabaseCurrent interface
|
||||
type MockDatabaseCurrent struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDatabaseCurrentMockRecorder
|
||||
}
|
||||
|
||||
// MockDatabaseCurrentMockRecorder is the mock recorder for MockDatabaseCurrent
|
||||
type MockDatabaseCurrentMockRecorder struct {
|
||||
mock *MockDatabaseCurrent
|
||||
}
|
||||
|
||||
// NewMockDatabaseCurrent creates a new mock instance
|
||||
func NewMockDatabaseCurrent(ctrl *gomock.Controller) *MockDatabaseCurrent {
|
||||
mock := &MockDatabaseCurrent{ctrl: ctrl}
|
||||
mock.recorder = &MockDatabaseCurrentMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockDatabaseCurrent) EXPECT() *MockDatabaseCurrentMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetURL mocks base method
|
||||
func (m *MockDatabaseCurrent) GetURL() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetURL")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetURL indicates an expected call of GetURL
|
||||
func (mr *MockDatabaseCurrentMockRecorder) GetURL() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetURL", reflect.TypeOf((*MockDatabaseCurrent)(nil).GetURL))
|
||||
}
|
||||
|
||||
// GetPort mocks base method
|
||||
func (m *MockDatabaseCurrent) GetPort() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPort")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetPort indicates an expected call of GetPort
|
||||
func (mr *MockDatabaseCurrentMockRecorder) GetPort() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPort", reflect.TypeOf((*MockDatabaseCurrent)(nil).GetPort))
|
||||
}
|
||||
|
||||
// GetReadyQuery mocks base method
|
||||
func (m *MockDatabaseCurrent) GetReadyQuery() operator.EnsureFunc {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetReadyQuery")
|
||||
ret0, _ := ret[0].(operator.EnsureFunc)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetReadyQuery indicates an expected call of GetReadyQuery
|
||||
func (mr *MockDatabaseCurrentMockRecorder) GetReadyQuery() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReadyQuery", reflect.TypeOf((*MockDatabaseCurrent)(nil).GetReadyQuery))
|
||||
}
|
||||
|
||||
// GetCertificateKey mocks base method
|
||||
func (m *MockDatabaseCurrent) GetCertificateKey() *rsa.PrivateKey {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCertificateKey")
|
||||
ret0, _ := ret[0].(*rsa.PrivateKey)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetCertificateKey indicates an expected call of GetCertificateKey
|
||||
func (mr *MockDatabaseCurrentMockRecorder) GetCertificateKey() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCertificateKey", reflect.TypeOf((*MockDatabaseCurrent)(nil).GetCertificateKey))
|
||||
}
|
||||
|
||||
// SetCertificateKey mocks base method
|
||||
func (m *MockDatabaseCurrent) SetCertificateKey(arg0 *rsa.PrivateKey) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetCertificateKey", arg0)
|
||||
}
|
||||
|
||||
// SetCertificateKey indicates an expected call of SetCertificateKey
|
||||
func (mr *MockDatabaseCurrentMockRecorder) SetCertificateKey(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCertificateKey", reflect.TypeOf((*MockDatabaseCurrent)(nil).SetCertificateKey), arg0)
|
||||
}
|
||||
|
||||
// GetCertificate mocks base method
|
||||
func (m *MockDatabaseCurrent) GetCertificate() []byte {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCertificate")
|
||||
ret0, _ := ret[0].([]byte)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetCertificate indicates an expected call of GetCertificate
|
||||
func (mr *MockDatabaseCurrentMockRecorder) GetCertificate() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCertificate", reflect.TypeOf((*MockDatabaseCurrent)(nil).GetCertificate))
|
||||
}
|
||||
|
||||
// SetCertificate mocks base method
|
||||
func (m *MockDatabaseCurrent) SetCertificate(arg0 []byte) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetCertificate", arg0)
|
||||
}
|
||||
|
||||
// SetCertificate indicates an expected call of SetCertificate
|
||||
func (mr *MockDatabaseCurrentMockRecorder) SetCertificate(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCertificate", reflect.TypeOf((*MockDatabaseCurrent)(nil).SetCertificate), arg0)
|
||||
}
|
||||
|
||||
// GetAddUserFunc mocks base method
|
||||
func (m *MockDatabaseCurrent) GetAddUserFunc() func(string) (operator.QueryFunc, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAddUserFunc")
|
||||
ret0, _ := ret[0].(func(string) (operator.QueryFunc, error))
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetAddUserFunc indicates an expected call of GetAddUserFunc
|
||||
func (mr *MockDatabaseCurrentMockRecorder) GetAddUserFunc() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddUserFunc", reflect.TypeOf((*MockDatabaseCurrent)(nil).GetAddUserFunc))
|
||||
}
|
||||
|
||||
// GetDeleteUserFunc mocks base method
|
||||
func (m *MockDatabaseCurrent) GetDeleteUserFunc() func(string) (operator.DestroyFunc, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetDeleteUserFunc")
|
||||
ret0, _ := ret[0].(func(string) (operator.DestroyFunc, error))
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetDeleteUserFunc indicates an expected call of GetDeleteUserFunc
|
||||
func (mr *MockDatabaseCurrentMockRecorder) GetDeleteUserFunc() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDeleteUserFunc", reflect.TypeOf((*MockDatabaseCurrent)(nil).GetDeleteUserFunc))
|
||||
}
|
||||
|
||||
// GetListUsersFunc mocks base method
|
||||
func (m *MockDatabaseCurrent) GetListUsersFunc() func(kubernetes.ClientInt) ([]string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetListUsersFunc")
|
||||
ret0, _ := ret[0].(func(kubernetes.ClientInt) ([]string, error))
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetListUsersFunc indicates an expected call of GetListUsersFunc
|
||||
func (mr *MockDatabaseCurrentMockRecorder) GetListUsersFunc() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListUsersFunc", reflect.TypeOf((*MockDatabaseCurrent)(nil).GetListUsersFunc))
|
||||
}
|
||||
|
||||
// GetListDatabasesFunc mocks base method
|
||||
func (m *MockDatabaseCurrent) GetListDatabasesFunc() func(kubernetes.ClientInt) ([]string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetListDatabasesFunc")
|
||||
ret0, _ := ret[0].(func(kubernetes.ClientInt) ([]string, error))
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetListDatabasesFunc indicates an expected call of GetListDatabasesFunc
|
||||
func (mr *MockDatabaseCurrentMockRecorder) GetListDatabasesFunc() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListDatabasesFunc", reflect.TypeOf((*MockDatabaseCurrent)(nil).GetListDatabasesFunc))
|
||||
}
|
||||
68
operator/database/kinds/databases/databases.go
Normal file
68
operator/database/kinds/databases/databases.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package databases
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/orbos/pkg/secret"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/provided"
|
||||
"github.com/pkg/errors"
|
||||
core "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
component = "database"
|
||||
)
|
||||
|
||||
func ComponentSelector() *labels.Selector {
|
||||
return labels.OpenComponentSelector("ZITADEL", component)
|
||||
}
|
||||
|
||||
func GetQueryAndDestroyFuncs(
|
||||
monitor mntr.Monitor,
|
||||
desiredTree *tree.Tree,
|
||||
currentTree *tree.Tree,
|
||||
namespace string,
|
||||
apiLabels *labels.API,
|
||||
timestamp string,
|
||||
nodeselector map[string]string,
|
||||
tolerations []core.Toleration,
|
||||
version string,
|
||||
features []string,
|
||||
) (
|
||||
query operator.QueryFunc,
|
||||
destroy operator.DestroyFunc,
|
||||
secrets map[string]*secret.Secret,
|
||||
err error,
|
||||
) {
|
||||
componentLabels := labels.MustForComponent(apiLabels, component)
|
||||
internalMonitor := monitor.WithField("component", component)
|
||||
|
||||
switch desiredTree.Common.Kind {
|
||||
case "databases.caos.ch/CockroachDB":
|
||||
return managed.AdaptFunc(componentLabels, namespace, timestamp, nodeselector, tolerations, version, features)(internalMonitor, desiredTree, currentTree)
|
||||
case "databases.caos.ch/ProvidedDatabase":
|
||||
return provided.AdaptFunc()(internalMonitor, desiredTree, currentTree)
|
||||
default:
|
||||
return nil, nil, nil, errors.Errorf("unknown database kind %s", desiredTree.Common.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func GetBackupList(
|
||||
monitor mntr.Monitor,
|
||||
desiredTree *tree.Tree,
|
||||
) (
|
||||
[]string,
|
||||
error,
|
||||
) {
|
||||
switch desiredTree.Common.Kind {
|
||||
case "databases.caos.ch/CockroachDB":
|
||||
return managed.BackupList()(monitor, desiredTree)
|
||||
case "databases.caos.ch/ProvidedDatabse":
|
||||
return nil, errors.Errorf("no backups supported for database kind %s", desiredTree.Common.Kind)
|
||||
default:
|
||||
return nil, errors.Errorf("unknown database kind %s", desiredTree.Common.Kind)
|
||||
}
|
||||
}
|
||||
256
operator/database/kinds/databases/managed/adapt.go
Normal file
256
operator/database/kinds/databases/managed/adapt.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package managed
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/operator"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
|
||||
"github.com/caos/orbos/pkg/secret"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources/pdb"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator/database/kinds/backups"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/core"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/rbac"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/services"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/statefulset"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
SfsName = "cockroachdb"
|
||||
pdbName = SfsName + "-budget"
|
||||
serviceAccountName = SfsName
|
||||
PublicServiceName = SfsName + "-public"
|
||||
privateServiceName = SfsName
|
||||
cockroachPort = int32(26257)
|
||||
cockroachHTTPPort = int32(8080)
|
||||
image = "cockroachdb/cockroach:v20.2.3"
|
||||
)
|
||||
|
||||
func AdaptFunc(
|
||||
componentLabels *labels.Component,
|
||||
namespace string,
|
||||
timestamp string,
|
||||
nodeselector map[string]string,
|
||||
tolerations []corev1.Toleration,
|
||||
version string,
|
||||
features []string,
|
||||
) func(
|
||||
monitor mntr.Monitor,
|
||||
desired *tree.Tree,
|
||||
current *tree.Tree,
|
||||
) (
|
||||
operator.QueryFunc,
|
||||
operator.DestroyFunc,
|
||||
map[string]*secret.Secret,
|
||||
error,
|
||||
) {
|
||||
|
||||
return func(
|
||||
monitor mntr.Monitor,
|
||||
desired *tree.Tree,
|
||||
current *tree.Tree,
|
||||
) (
|
||||
operator.QueryFunc,
|
||||
operator.DestroyFunc,
|
||||
map[string]*secret.Secret,
|
||||
error,
|
||||
) {
|
||||
internalMonitor := monitor.WithField("kind", "cockroachdb")
|
||||
allSecrets := map[string]*secret.Secret{}
|
||||
|
||||
desiredKind, err := parseDesiredV0(desired)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(err, "parsing desired state failed")
|
||||
}
|
||||
desired.Parsed = desiredKind
|
||||
|
||||
if !monitor.IsVerbose() && desiredKind.Spec.Verbose {
|
||||
internalMonitor.Verbose()
|
||||
}
|
||||
|
||||
var (
|
||||
isFeatureDatabase bool
|
||||
isFeatureRestore bool
|
||||
)
|
||||
for _, feature := range features {
|
||||
switch feature {
|
||||
case "database":
|
||||
isFeatureDatabase = true
|
||||
case "restore":
|
||||
isFeatureRestore = true
|
||||
}
|
||||
}
|
||||
|
||||
queryCert, destroyCert, addUser, deleteUser, listUsers, err := certificate.AdaptFunc(internalMonitor, namespace, componentLabels, desiredKind.Spec.ClusterDns, isFeatureDatabase)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
addRoot, err := addUser("root")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
destroyRoot, err := deleteUser("root")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
queryRBAC, destroyRBAC, err := rbac.AdaptFunc(internalMonitor, namespace, labels.MustForName(componentLabels, serviceAccountName))
|
||||
|
||||
cockroachNameLabels := labels.MustForName(componentLabels, SfsName)
|
||||
cockroachSelector := labels.DeriveNameSelector(cockroachNameLabels, false)
|
||||
cockroachSelectabel := labels.AsSelectable(cockroachNameLabels)
|
||||
querySFS, destroySFS, ensureInit, checkDBReady, listDatabases, err := statefulset.AdaptFunc(
|
||||
internalMonitor,
|
||||
cockroachSelectabel,
|
||||
cockroachSelector,
|
||||
desiredKind.Spec.Force,
|
||||
namespace,
|
||||
image,
|
||||
serviceAccountName,
|
||||
desiredKind.Spec.ReplicaCount,
|
||||
desiredKind.Spec.StorageCapacity,
|
||||
cockroachPort,
|
||||
cockroachHTTPPort,
|
||||
desiredKind.Spec.StorageClass,
|
||||
desiredKind.Spec.NodeSelector,
|
||||
desiredKind.Spec.Tolerations,
|
||||
desiredKind.Spec.Resources,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
queryS, destroyS, err := services.AdaptFunc(
|
||||
internalMonitor,
|
||||
namespace,
|
||||
labels.MustForName(componentLabels, PublicServiceName),
|
||||
labels.MustForName(componentLabels, privateServiceName),
|
||||
cockroachSelector,
|
||||
cockroachPort,
|
||||
cockroachHTTPPort,
|
||||
)
|
||||
|
||||
//externalName := "cockroachdb-public." + namespaceStr + ".svc.cluster.local"
|
||||
//queryES, destroyES, err := service.AdaptFunc("cockroachdb-public", "default", labels, []service.Port{}, "ExternalName", map[string]string{}, false, "", externalName)
|
||||
//if err != nil {
|
||||
// return nil, nil, err
|
||||
//}
|
||||
|
||||
queryPDB, err := pdb.AdaptFuncToEnsure(namespace, labels.MustForName(componentLabels, pdbName), cockroachSelector, "1")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
destroyPDB, err := pdb.AdaptFuncToDestroy(namespace, pdbName)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
currentDB := &Current{
|
||||
Common: &tree.Common{
|
||||
Kind: "databases.caos.ch/CockroachDB",
|
||||
Version: "v0",
|
||||
},
|
||||
Current: &CurrentDB{
|
||||
CA: &certificate.Current{},
|
||||
},
|
||||
}
|
||||
current.Parsed = currentDB
|
||||
|
||||
queriers := make([]operator.QueryFunc, 0)
|
||||
if isFeatureDatabase {
|
||||
queriers = append(queriers,
|
||||
queryRBAC,
|
||||
queryCert,
|
||||
addRoot,
|
||||
operator.ResourceQueryToZitadelQuery(querySFS),
|
||||
operator.ResourceQueryToZitadelQuery(queryPDB),
|
||||
queryS,
|
||||
operator.EnsureFuncToQueryFunc(ensureInit),
|
||||
)
|
||||
}
|
||||
|
||||
destroyers := make([]operator.DestroyFunc, 0)
|
||||
if isFeatureDatabase {
|
||||
destroyers = append(destroyers,
|
||||
operator.ResourceDestroyToZitadelDestroy(destroyPDB),
|
||||
destroyS,
|
||||
operator.ResourceDestroyToZitadelDestroy(destroySFS),
|
||||
destroyRBAC,
|
||||
destroyCert,
|
||||
destroyRoot,
|
||||
)
|
||||
}
|
||||
|
||||
if desiredKind.Spec.Backups != nil {
|
||||
|
||||
oneBackup := false
|
||||
for backupName := range desiredKind.Spec.Backups {
|
||||
if timestamp != "" && strings.HasPrefix(timestamp, backupName) {
|
||||
oneBackup = true
|
||||
}
|
||||
}
|
||||
|
||||
for backupName, desiredBackup := range desiredKind.Spec.Backups {
|
||||
currentBackup := &tree.Tree{}
|
||||
if timestamp == "" || !oneBackup || (timestamp != "" && strings.HasPrefix(timestamp, backupName)) {
|
||||
queryB, destroyB, secrets, err := backups.GetQueryAndDestroyFuncs(
|
||||
internalMonitor,
|
||||
desiredBackup,
|
||||
currentBackup,
|
||||
backupName,
|
||||
namespace,
|
||||
componentLabels,
|
||||
checkDBReady,
|
||||
strings.TrimPrefix(timestamp, backupName+"."),
|
||||
nodeselector,
|
||||
tolerations,
|
||||
version,
|
||||
features,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
secret.AppendSecrets(backupName, allSecrets, secrets)
|
||||
destroyers = append(destroyers, destroyB)
|
||||
queriers = append(queriers, queryB)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return func(k8sClient kubernetes.ClientInt, queried map[string]interface{}) (operator.EnsureFunc, error) {
|
||||
if !isFeatureRestore {
|
||||
queriedCurrentDB, err := core.ParseQueriedForDatabase(queried)
|
||||
if err != nil || queriedCurrentDB == nil {
|
||||
// TODO: query system state
|
||||
currentDB.Current.Port = strconv.Itoa(int(cockroachPort))
|
||||
currentDB.Current.URL = PublicServiceName
|
||||
currentDB.Current.ReadyFunc = checkDBReady
|
||||
currentDB.Current.AddUserFunc = addUser
|
||||
currentDB.Current.DeleteUserFunc = deleteUser
|
||||
currentDB.Current.ListUsersFunc = listUsers
|
||||
currentDB.Current.ListDatabasesFunc = listDatabases
|
||||
|
||||
core.SetQueriedForDatabase(queried, current)
|
||||
internalMonitor.Info("set current state of managed database")
|
||||
}
|
||||
}
|
||||
|
||||
ensure, err := operator.QueriersToEnsureFunc(internalMonitor, true, queriers, k8sClient, queried)
|
||||
return ensure, err
|
||||
},
|
||||
operator.DestroyersToDestroyFunc(internalMonitor, destroyers),
|
||||
allSecrets,
|
||||
nil
|
||||
}
|
||||
}
|
||||
177
operator/database/kinds/databases/managed/adapt_backup_test.go
Normal file
177
operator/database/kinds/databases/managed/adapt_backup_test.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package managed
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
kubernetesmock "github.com/caos/orbos/pkg/kubernetes/mock"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/orbos/pkg/secret"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator/database/kinds/backups/bucket"
|
||||
"github.com/caos/zitadel/operator/database/kinds/backups/bucket/backup"
|
||||
"github.com/caos/zitadel/operator/database/kinds/backups/bucket/clean"
|
||||
"github.com/caos/zitadel/operator/database/kinds/backups/bucket/restore"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getTreeWithDBAndBackup(t *testing.T, masterkey string, saJson string, backupName string) *tree.Tree {
|
||||
|
||||
bucketDesired := getDesiredTree(t, masterkey, &bucket.DesiredV0{
|
||||
Common: &tree.Common{
|
||||
Kind: "databases.caos.ch/BucketBackup",
|
||||
Version: "v0",
|
||||
},
|
||||
Spec: &bucket.Spec{
|
||||
Verbose: true,
|
||||
Cron: "testCron",
|
||||
Bucket: "testBucket",
|
||||
ServiceAccountJSON: &secret.Secret{
|
||||
Value: saJson,
|
||||
},
|
||||
},
|
||||
})
|
||||
bucketDesiredKind, err := bucket.ParseDesiredV0(bucketDesired)
|
||||
assert.NoError(t, err)
|
||||
bucketDesired.Parsed = bucketDesiredKind
|
||||
|
||||
return getDesiredTree(t, masterkey, &DesiredV0{
|
||||
Common: &tree.Common{
|
||||
Kind: "databases.caos.ch/CockroachDB",
|
||||
Version: "v0",
|
||||
},
|
||||
Spec: Spec{
|
||||
Verbose: false,
|
||||
ReplicaCount: 1,
|
||||
StorageCapacity: "368Gi",
|
||||
StorageClass: "testSC",
|
||||
NodeSelector: map[string]string{},
|
||||
ClusterDns: "testDns",
|
||||
Backups: map[string]*tree.Tree{backupName: bucketDesired},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestManaged_AdaptBucketBackup(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "testKind", "v0"), "database")
|
||||
|
||||
labels := map[string]string{
|
||||
"app.kubernetes.io/component": "backup",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "backup-serviceaccountjson",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"app.kubernetes.io/version": "testVersion",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "BucketBackup",
|
||||
}
|
||||
namespace := "testNs"
|
||||
timestamp := "testTs"
|
||||
nodeselector := map[string]string{"test": "test"}
|
||||
tolerations := []corev1.Toleration{}
|
||||
version := "testVersion"
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
backupName := "testBucket"
|
||||
saJson := "testSA"
|
||||
masterkey := "testMk"
|
||||
|
||||
desired := getTreeWithDBAndBackup(t, masterkey, saJson, backupName)
|
||||
|
||||
features := []string{backup.Normal}
|
||||
bucket.SetBackup(k8sClient, namespace, labels, saJson)
|
||||
k8sClient.EXPECT().WaitUntilStatefulsetIsReady(namespace, SfsName, true, true, time.Duration(60))
|
||||
|
||||
query, _, _, err := AdaptFunc(componentLabels, namespace, timestamp, nodeselector, tolerations, version, features)(monitor, desired, &tree.Tree{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
databases := []string{"test1", "test2"}
|
||||
queried := bucket.SetQueriedForDatabases(databases)
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestManaged_AdaptBucketInstantBackup(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "testKind", "v0"), "database")
|
||||
labels := map[string]string{
|
||||
"app.kubernetes.io/component": "backup",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "backup-serviceaccountjson",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"app.kubernetes.io/version": "testVersion",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "BucketBackup",
|
||||
}
|
||||
namespace := "testNs"
|
||||
timestamp := "testTs"
|
||||
nodeselector := map[string]string{"test": "test"}
|
||||
tolerations := []corev1.Toleration{}
|
||||
version := "testVersion"
|
||||
masterkey := "testMk"
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
saJson := "testSA"
|
||||
backupName := "testBucket"
|
||||
|
||||
features := []string{backup.Instant}
|
||||
bucket.SetInstantBackup(k8sClient, namespace, backupName, labels, saJson)
|
||||
k8sClient.EXPECT().WaitUntilStatefulsetIsReady(namespace, SfsName, true, true, time.Duration(60))
|
||||
|
||||
desired := getTreeWithDBAndBackup(t, masterkey, saJson, backupName)
|
||||
|
||||
query, _, _, err := AdaptFunc(componentLabels, namespace, timestamp, nodeselector, tolerations, version, features)(monitor, desired, &tree.Tree{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
databases := []string{"test1", "test2"}
|
||||
queried := bucket.SetQueriedForDatabases(databases)
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestManaged_AdaptBucketCleanAndRestore(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "testKind", "v0"), "database")
|
||||
labels := map[string]string{
|
||||
"app.kubernetes.io/component": "backup",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "backup-serviceaccountjson",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"app.kubernetes.io/version": "testVersion",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "BucketBackup",
|
||||
}
|
||||
namespace := "testNs"
|
||||
timestamp := "testTs"
|
||||
nodeselector := map[string]string{"test": "test"}
|
||||
tolerations := []corev1.Toleration{}
|
||||
version := "testVersion"
|
||||
masterkey := "testMk"
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
saJson := "testSA"
|
||||
backupName := "testBucket"
|
||||
|
||||
features := []string{restore.Instant, clean.Instant}
|
||||
bucket.SetRestore(k8sClient, namespace, backupName, labels, saJson)
|
||||
bucket.SetClean(k8sClient, namespace, backupName)
|
||||
k8sClient.EXPECT().WaitUntilStatefulsetIsReady(namespace, SfsName, true, true, time.Duration(60)).Times(2)
|
||||
|
||||
desired := getTreeWithDBAndBackup(t, masterkey, saJson, backupName)
|
||||
|
||||
query, _, _, err := AdaptFunc(componentLabels, namespace, timestamp, nodeselector, tolerations, version, features)(monitor, desired, &tree.Tree{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
databases := []string{"test1", "test2"}
|
||||
queried := bucket.SetQueriedForDatabases(databases)
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
255
operator/database/kinds/databases/managed/adapt_test.go
Normal file
255
operator/database/kinds/databases/managed/adapt_test.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package managed
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caos/orbos/mntr"
|
||||
kubernetesmock "github.com/caos/orbos/pkg/kubernetes/mock"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/orbos/pkg/secret"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
coremock "github.com/caos/zitadel/operator/database/kinds/databases/core/mock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
policy "k8s.io/api/policy/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
func getDesiredTree(t *testing.T, masterkey string, desired interface{}) *tree.Tree {
|
||||
secret.Masterkey = masterkey
|
||||
|
||||
desiredTree := &tree.Tree{}
|
||||
data, err := yaml.Marshal(desired)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, yaml.Unmarshal(data, desiredTree))
|
||||
|
||||
return desiredTree
|
||||
}
|
||||
|
||||
func TestManaged_Adapt1(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
|
||||
nodeLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "database",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "cockroachdb.node",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
|
||||
cockroachLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "database",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "cockroachdb-budget",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"app.kubernetes.io/version": "testVersion",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "testKind",
|
||||
}
|
||||
|
||||
cockroachSelectorLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "database",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "cockroachdb",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "testKind", "v0"), "database")
|
||||
|
||||
namespace := "testNs"
|
||||
timestamp := "testTs"
|
||||
nodeselector := map[string]string{"test": "test"}
|
||||
tolerations := []corev1.Toleration{}
|
||||
version := "testVersion"
|
||||
features := []string{"database"}
|
||||
masterkey := "testMk"
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
queried := map[string]interface{}{}
|
||||
|
||||
desired := getDesiredTree(t, masterkey, &DesiredV0{
|
||||
Common: &tree.Common{
|
||||
Kind: "databases.caos.ch/CockroachDB",
|
||||
Version: "v0",
|
||||
},
|
||||
Spec: Spec{
|
||||
Verbose: false,
|
||||
ReplicaCount: 1,
|
||||
StorageCapacity: "368Gi",
|
||||
StorageClass: "testSC",
|
||||
NodeSelector: map[string]string{},
|
||||
ClusterDns: "testDns",
|
||||
},
|
||||
})
|
||||
|
||||
unav := intstr.FromInt(1)
|
||||
k8sClient.EXPECT().ApplyPodDisruptionBudget(&policy.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "cockroachdb-budget",
|
||||
Namespace: namespace,
|
||||
Labels: cockroachLabels,
|
||||
},
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: cockroachSelectorLabels,
|
||||
},
|
||||
MaxUnavailable: &unav,
|
||||
},
|
||||
})
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ApplyService(gomock.Any()).Times(3)
|
||||
k8sClient.EXPECT().ApplyServiceAccount(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplyRole(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplyClusterRole(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplyRoleBinding(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplyClusterRoleBinding(gomock.Any()).Times(1)
|
||||
//statefulset
|
||||
k8sClient.EXPECT().ApplyStatefulSet(gomock.Any(), gomock.Any()).Times(1)
|
||||
//running for setup
|
||||
k8sClient.EXPECT().WaitUntilStatefulsetIsReady(namespace, SfsName, true, false, time.Duration(60))
|
||||
//not ready for setup
|
||||
k8sClient.EXPECT().WaitUntilStatefulsetIsReady(namespace, SfsName, true, true, time.Duration(1))
|
||||
//ready after setup
|
||||
k8sClient.EXPECT().WaitUntilStatefulsetIsReady(namespace, SfsName, true, true, time.Duration(60))
|
||||
//client
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(nil)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(nil)
|
||||
k8sClient.EXPECT().ApplySecret(gomock.Any()).Times(1)
|
||||
//node
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(nil)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(nil)
|
||||
dbCurrent.EXPECT().SetCertificate(gomock.Any()).Times(1)
|
||||
dbCurrent.EXPECT().SetCertificateKey(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplySecret(gomock.Any()).Times(1)
|
||||
|
||||
query, _, _, err := AdaptFunc(componentLabels, namespace, timestamp, nodeselector, tolerations, version, features)(monitor, desired, &tree.Tree{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestManaged_Adapt2(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
timestamp := "testTs"
|
||||
|
||||
nodeLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "database2",
|
||||
"app.kubernetes.io/managed-by": "testOp2",
|
||||
"app.kubernetes.io/name": "cockroachdb.node",
|
||||
"app.kubernetes.io/part-of": "testProd2",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
|
||||
cockroachLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "database2",
|
||||
"app.kubernetes.io/managed-by": "testOp2",
|
||||
"app.kubernetes.io/name": "cockroachdb-budget",
|
||||
"app.kubernetes.io/part-of": "testProd2",
|
||||
"app.kubernetes.io/version": "testVersion2",
|
||||
"caos.ch/apiversion": "v1",
|
||||
"caos.ch/kind": "testKind2",
|
||||
}
|
||||
|
||||
cockroachSelectorLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "database2",
|
||||
"app.kubernetes.io/managed-by": "testOp2",
|
||||
"app.kubernetes.io/name": "cockroachdb",
|
||||
"app.kubernetes.io/part-of": "testProd2",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd2", "testOp2", "testVersion2"), "testKind2", "v1"), "database2")
|
||||
|
||||
nodeselector := map[string]string{"test2": "test2"}
|
||||
var tolerations []corev1.Toleration
|
||||
version := "testVersion2"
|
||||
features := []string{"database"}
|
||||
masterkey := "testMk2"
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
queried := map[string]interface{}{}
|
||||
|
||||
desired := getDesiredTree(t, masterkey, &DesiredV0{
|
||||
Common: &tree.Common{
|
||||
Kind: "databases.caos.ch/CockroachDB",
|
||||
Version: "v0",
|
||||
},
|
||||
Spec: Spec{
|
||||
Verbose: false,
|
||||
ReplicaCount: 1,
|
||||
StorageCapacity: "368Gi",
|
||||
StorageClass: "testSC",
|
||||
NodeSelector: map[string]string{},
|
||||
ClusterDns: "testDns",
|
||||
},
|
||||
})
|
||||
|
||||
unav := intstr.FromInt(1)
|
||||
k8sClient.EXPECT().ApplyPodDisruptionBudget(&policy.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "cockroachdb-budget",
|
||||
Namespace: namespace,
|
||||
Labels: cockroachLabels,
|
||||
},
|
||||
Spec: policy.PodDisruptionBudgetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: cockroachSelectorLabels,
|
||||
},
|
||||
MaxUnavailable: &unav,
|
||||
},
|
||||
})
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ApplyService(gomock.Any()).Times(3)
|
||||
k8sClient.EXPECT().ApplyServiceAccount(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplyRole(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplyClusterRole(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplyRoleBinding(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplyClusterRoleBinding(gomock.Any()).Times(1)
|
||||
//statefulset
|
||||
k8sClient.EXPECT().ApplyStatefulSet(gomock.Any(), gomock.Any()).Times(1)
|
||||
//running for setup
|
||||
k8sClient.EXPECT().WaitUntilStatefulsetIsReady(namespace, SfsName, true, false, time.Duration(60))
|
||||
//not ready for setup
|
||||
k8sClient.EXPECT().WaitUntilStatefulsetIsReady(namespace, SfsName, true, true, time.Duration(1))
|
||||
//ready after setup
|
||||
k8sClient.EXPECT().WaitUntilStatefulsetIsReady(namespace, SfsName, true, true, time.Duration(60))
|
||||
//client
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(nil)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(nil)
|
||||
k8sClient.EXPECT().ApplySecret(gomock.Any()).Times(1)
|
||||
//node
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(nil)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(nil)
|
||||
dbCurrent.EXPECT().SetCertificate(gomock.Any()).Times(1)
|
||||
dbCurrent.EXPECT().SetCertificateKey(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplySecret(gomock.Any()).Times(1)
|
||||
|
||||
query, _, _, err := AdaptFunc(componentLabels, namespace, timestamp, nodeselector, tolerations, version, features)(monitor, desired, &tree.Tree{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/zitadel/operator"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate/client"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate/node"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeSecret = "cockroachdb.node"
|
||||
)
|
||||
|
||||
func AdaptFunc(
|
||||
monitor mntr.Monitor,
|
||||
namespace string,
|
||||
componentLabels *labels.Component,
|
||||
clusterDns string,
|
||||
generateNodeIfNotExists bool,
|
||||
) (
|
||||
operator.QueryFunc,
|
||||
operator.DestroyFunc,
|
||||
func(user string) (operator.QueryFunc, error),
|
||||
func(user string) (operator.DestroyFunc, error),
|
||||
func(k8sClient kubernetes.ClientInt) ([]string, error),
|
||||
error,
|
||||
) {
|
||||
cMonitor := monitor.WithField("type", "certificates")
|
||||
|
||||
queryNode, destroyNode, err := node.AdaptFunc(
|
||||
cMonitor,
|
||||
namespace,
|
||||
labels.MustForName(componentLabels, nodeSecret),
|
||||
clusterDns,
|
||||
generateNodeIfNotExists,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
queriers := []operator.QueryFunc{
|
||||
queryNode,
|
||||
}
|
||||
|
||||
destroyers := []operator.DestroyFunc{
|
||||
destroyNode,
|
||||
}
|
||||
|
||||
return func(k8sClient kubernetes.ClientInt, queried map[string]interface{}) (operator.EnsureFunc, error) {
|
||||
return operator.QueriersToEnsureFunc(cMonitor, false, queriers, k8sClient, queried)
|
||||
},
|
||||
operator.DestroyersToDestroyFunc(cMonitor, destroyers),
|
||||
func(user string) (operator.QueryFunc, error) {
|
||||
query, _, err := client.AdaptFunc(
|
||||
cMonitor,
|
||||
namespace,
|
||||
componentLabels,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queryClient := query(user)
|
||||
|
||||
return func(k8sClient kubernetes.ClientInt, queried map[string]interface{}) (operator.EnsureFunc, error) {
|
||||
_, err := queryNode(k8sClient, queried)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return queryClient(k8sClient, queried)
|
||||
}, nil
|
||||
},
|
||||
func(user string) (operator.DestroyFunc, error) {
|
||||
_, destroy, err := client.AdaptFunc(
|
||||
cMonitor,
|
||||
namespace,
|
||||
componentLabels,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return destroy(user), nil
|
||||
},
|
||||
func(k8sClient kubernetes.ClientInt) ([]string, error) {
|
||||
return client.QueryCertificates(namespace, labels.DeriveComponentSelector(componentLabels, false), k8sClient)
|
||||
},
|
||||
nil
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
kubernetesmock "github.com/caos/orbos/pkg/kubernetes/mock"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/core"
|
||||
coremock "github.com/caos/zitadel/operator/database/kinds/databases/core/mock"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate/pem"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
caPem = `-----BEGIN CERTIFICATE-----
|
||||
MIIE9TCCAt2gAwIBAgICB+MwDQYJKoZIhvcNAQELBQAwKzESMBAGA1UEChMJQ29j
|
||||
a3JvYWNoMRUwEwYDVQQDEwxDb2Nrcm9hY2ggQ0EwHhcNMjAxMjAxMTAyMjA0WhcN
|
||||
MzAxMjAxMTAyMjA0WjArMRIwEAYDVQQKEwlDb2Nrcm9hY2gxFTATBgNVBAMTDENv
|
||||
Y2tyb2FjaCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOja5IXJ
|
||||
GUY9sFgyvWkav+O74gzcv8y69uJzSOx0piOP+sfpZWVeGEjqO4JgdcS5NPMrT5Tb
|
||||
aJ52CjiOdlHVTyR87i5JmOIvA2qA4dWQmSbX7AQ8r9ptYDe9xMn+qFegcbr4YxCz
|
||||
K9mmbDZUhlLO7cz3QV6nvRxGFWbzffo8BXZnOUCAOyOHrbnPpLumnfZlL5BckdtY
|
||||
pS7jAlUpKSMBTK4AHcmrouFsNKHqlUopYXeJFdg9g1F0DuCnVP9x7+XcUW8dAVut
|
||||
Q7Jswy+++GAXs6mPVsYFLXUSYNyW+Bfl/jKwx0XTQx/6iyNpK0XtAzjBZFjXwmot
|
||||
0mODkqnfE3BB4lXxZ5knomAQEGSScUhCUb9upbF4uJJF27xr/kIkwtWxMGpCXds0
|
||||
IxI+wNRCenhfFZEIQCzri0zn6WdN8b/gbv1BErNcccYwolPUv1oUgYbzowbQ5O2D
|
||||
aLQPqO1VAZiZHLxb787bRywpCl33VZ1ptMHi2ogKjcsh4DQ9SsRj+rU/Tk5lyk7G
|
||||
FHteyHcq12TGpz9/CQYMacl8yeRRHfNO3Rq0jFTYeD4+ZdVBPKeuTHyXGzy2T157
|
||||
pgqMFzwqxlNYPzpuz7xsZBExJzCtcomB8fMlsCJnxV/kuMTTsWsPrRc7hsqzBCq3
|
||||
plfYT8a7EBCVmJmDu+6mMVh+A9M2zZCqtV57AgMBAAGjIzAhMA4GA1UdDwEB/wQE
|
||||
AwICpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQDRmkBYDk/F
|
||||
lYTgMaL7yhRAowDR8mtOl06Oba/MCDDsmBvfOVIqY9Sa9sJtb78Uvecv9WAE4qpb
|
||||
PNSaIvNmujmGQrBxtGwQdeHZvtXQ1lKAGBM+ukfz/NFU+6rwOdTuLgS5jTaiM4r1
|
||||
uMoIG6S5z/brWGHAw8Qv2Fq05dSLnevmPWK903V/RnwmCKT9as5Qpfkaie2PNory
|
||||
euxVGeGolxzgbSps3ZJlDSSymQBl10iJYYdGIsgcw5FHcCdpS4AutdpQbIFuCGk2
|
||||
CHTcTTa4KMaVfRm/gKm1gh8UnuVDLBKQS8+1CHFYUnih8ozBNUyBo5r5L4BHJK/n
|
||||
f0gnrYqaxtqPAyHkfo0PRc50HlAQRS/gW8ESv2PQmcSWlDggEzXt5MqFIYOfEXWE
|
||||
gtC7Ct1P7gzIRxolYVsNSgBR62sJM1PUa39E5v0QJsmuM51txHuw/PGqkkBlEwx9
|
||||
4u3FFXD9Pslg/g1l50Ek5O3/TWMta4Q+NagDDaOdkb4LRQr522ySgGIinBw00Xr+
|
||||
Ics2L42rP1LQyaE7+BljDEwmGaT7mrl8QGZR9uv/9mbtk2+q7wAOQzcMSFFACfwx
|
||||
KdKENmK0GpQZ6HaeDZO7wxGiAyFKhTNpd39XNOEhaKpWLSUxnZTLGlMIzvSyMGCj
|
||||
PV3xm+Xg1Xvki7f/qC1djysMcaokoJzNdg==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
caPrivPem = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
)
|
||||
|
||||
func TestCertificate_AdaptWithCA(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
clusterDns := "testDns"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "cockroachdb")
|
||||
|
||||
nodeLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "cockroachdb",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "cockroachdb.node",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{},
|
||||
}
|
||||
ca, err := pem.DecodeCertificate([]byte(caPem))
|
||||
assert.NoError(t, err)
|
||||
caPriv, err := pem.DecodeKey([]byte(caPrivPem))
|
||||
assert.NoError(t, err)
|
||||
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(ca)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(caPriv)
|
||||
dbCurrent.EXPECT().SetCertificate(ca).Times(1)
|
||||
dbCurrent.EXPECT().SetCertificateKey(caPriv).Times(1)
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
current := &tree.Tree{
|
||||
Parsed: dbCurrent,
|
||||
}
|
||||
|
||||
core.SetQueriedForDatabase(queried, current)
|
||||
|
||||
query, _, _, _, _, err := AdaptFunc(monitor, namespace, componentLabels, clusterDns, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestNode_AdaptWithoutCA(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
clusterDns := "testDns"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "cockroachdb")
|
||||
|
||||
nodeLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "cockroachdb",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "cockroachdb.node",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(nil)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(nil)
|
||||
dbCurrent.EXPECT().SetCertificate(gomock.Any()).Times(1)
|
||||
dbCurrent.EXPECT().SetCertificateKey(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplySecret(gomock.Any()).Times(1).Return(nil)
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
current := &tree.Tree{
|
||||
Parsed: dbCurrent,
|
||||
}
|
||||
|
||||
core.SetQueriedForDatabase(queried, current)
|
||||
|
||||
query, _, _, _, _, err := AdaptFunc(monitor, namespace, componentLabels, clusterDns, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestNode_AdaptAlreadyExisting(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
clusterDns := "testDns"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "cockroachdb")
|
||||
|
||||
nodeLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "cockroachdb",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "cockroachdb.node",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
caCertKey := "ca.crt"
|
||||
caPrivKeyKey := "ca.key"
|
||||
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{{
|
||||
ObjectMeta: metav1.ObjectMeta{},
|
||||
Data: map[string][]byte{
|
||||
caCertKey: []byte(caPem),
|
||||
caPrivKeyKey: []byte(caPrivPem),
|
||||
},
|
||||
Type: "Opaque",
|
||||
}},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
dbCurrent.EXPECT().SetCertificate(gomock.Any()).Times(1)
|
||||
dbCurrent.EXPECT().SetCertificateKey(gomock.Any()).Times(1)
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
current := &tree.Tree{
|
||||
Parsed: dbCurrent,
|
||||
}
|
||||
|
||||
core.SetQueriedForDatabase(queried, current)
|
||||
|
||||
query, _, _, _, _, err := AdaptFunc(monitor, namespace, componentLabels, clusterDns, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestNode_AdaptCreateUser(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
clusterDns := "testDns"
|
||||
namespace := "testNs"
|
||||
user := "test"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "cockroachdb")
|
||||
|
||||
nodeLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "cockroachdb",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "cockroachdb.node",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
ca, err := pem.DecodeCertificate([]byte(caPem))
|
||||
assert.NoError(t, err)
|
||||
caPriv, err := pem.DecodeKey([]byte(caPrivPem))
|
||||
assert.NoError(t, err)
|
||||
|
||||
caCertKey := "ca.crt"
|
||||
caPrivKeyKey := "ca.key"
|
||||
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{{
|
||||
ObjectMeta: metav1.ObjectMeta{},
|
||||
Data: map[string][]byte{
|
||||
caCertKey: []byte(caPem),
|
||||
caPrivKeyKey: []byte(caPrivPem),
|
||||
},
|
||||
Type: "Opaque",
|
||||
}},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(ca)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(caPriv)
|
||||
dbCurrent.EXPECT().SetCertificate(gomock.Any()).Times(1)
|
||||
dbCurrent.EXPECT().SetCertificateKey(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplySecret(gomock.Any())
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
current := &tree.Tree{
|
||||
Parsed: dbCurrent,
|
||||
}
|
||||
core.SetQueriedForDatabase(queried, current)
|
||||
|
||||
_, _, createUser, _, _, err := AdaptFunc(monitor, namespace, componentLabels, clusterDns, true)
|
||||
assert.NoError(t, err)
|
||||
query, err := createUser(user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewCA() (*rsa.PrivateKey, []byte, error) {
|
||||
ca := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(2019),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Cockroach"},
|
||||
CommonName: "Cockroach CA",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||
IsCA: true,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return caPrivKey, caBytes, nil
|
||||
}
|
||||
|
||||
func NewClient(caPrivKey *rsa.PrivateKey, ca []byte, user string) (*rsa.PrivateKey, []byte, error) {
|
||||
cert := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1658),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Cockroach"},
|
||||
CommonName: user,
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
}
|
||||
|
||||
certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caCert, err := x509.ParseCertificate(ca)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, cert, caCert, &certPrivKey.PublicKey, caPrivKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return certPrivKey, certBytes, nil
|
||||
}
|
||||
|
||||
func NewNode(caPrivKey *rsa.PrivateKey, ca []byte, namespace string, clusterDns string) (*rsa.PrivateKey, []byte, error) {
|
||||
cert := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1658),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Cockroach"},
|
||||
CommonName: "node",
|
||||
},
|
||||
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
DNSNames: []string{
|
||||
"localhost",
|
||||
"cockroachdb-public",
|
||||
"cockroachdb-public.default",
|
||||
"cockroachdb-public." + namespace,
|
||||
"cockroachdb-public." + namespace + ".svc." + clusterDns,
|
||||
"*.cockroachdb",
|
||||
"*.cockroachdb." + namespace,
|
||||
"*.cockroachdb." + namespace + ".svc." + clusterDns,
|
||||
},
|
||||
}
|
||||
|
||||
certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caCert, err := x509.ParseCertificate(ca)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, cert, caCert, &certPrivKey.PublicKey, caPrivKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return certPrivKey, certBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate/pem"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCertificates_CAE(t *testing.T) {
|
||||
priv, rootCa, err := NewCA()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, priv)
|
||||
|
||||
pemCa, err := pem.EncodeCertificate(rootCa)
|
||||
pemkey, err := pem.EncodeKey(priv)
|
||||
assert.NotNil(t, pemCa)
|
||||
assert.NotNil(t, pemkey)
|
||||
|
||||
_, err = x509.ParseCertificate(rootCa)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCertificates_CA(t *testing.T) {
|
||||
_, rootCa, err := NewCA()
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = x509.ParseCertificate(rootCa)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCertificates_Chain(t *testing.T) {
|
||||
rootKey, rootCert, err := NewCA()
|
||||
assert.NoError(t, err)
|
||||
rootPem, err := pem.EncodeCertificate(rootCert)
|
||||
assert.NoError(t, err)
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
ok := roots.AppendCertsFromPEM(rootPem)
|
||||
assert.Equal(t, ok, true)
|
||||
|
||||
_, clientCert, err := NewClient(rootKey, rootCert, "test")
|
||||
|
||||
cert, err := x509.ParseCertificate(clientCert)
|
||||
assert.NoError(t, err)
|
||||
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: roots,
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
_, err = cert.Verify(opts)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/caos/zitadel/operator"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources/secret"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/core"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate/certificates"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate/pem"
|
||||
)
|
||||
|
||||
const (
|
||||
clientSecretPrefix = "cockroachdb.client."
|
||||
caCertKey = "ca.crt"
|
||||
clientCertKeyPrefix = "client."
|
||||
clientCertKeySuffix = ".crt"
|
||||
clientPrivKeyKeyPrefix = "client."
|
||||
clientPrivKeyKeySuffix = ".key"
|
||||
)
|
||||
|
||||
func AdaptFunc(
|
||||
monitor mntr.Monitor,
|
||||
namespace string,
|
||||
componentLabels *labels.Component,
|
||||
) (
|
||||
func(client string) operator.QueryFunc,
|
||||
func(client string) operator.DestroyFunc,
|
||||
error,
|
||||
) {
|
||||
|
||||
return func(client string) operator.QueryFunc {
|
||||
clientSecret := clientSecretPrefix + client
|
||||
nameLabels := labels.MustForName(componentLabels, strings.ReplaceAll(clientSecret, "_", "-"))
|
||||
clientCertKey := clientCertKeyPrefix + client + clientCertKeySuffix
|
||||
clientPrivKeyKey := clientPrivKeyKeyPrefix + client + clientPrivKeyKeySuffix
|
||||
|
||||
return func(k8sClient kubernetes.ClientInt, queried map[string]interface{}) (operator.EnsureFunc, error) {
|
||||
queriers := make([]operator.QueryFunc, 0)
|
||||
|
||||
currentDB, err := core.ParseQueriedForDatabase(queried)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
caCert := currentDB.GetCertificate()
|
||||
caKey := currentDB.GetCertificateKey()
|
||||
if caKey == nil || caCert == nil || len(caCert) == 0 {
|
||||
return nil, errors.New("no ca-certificate found")
|
||||
}
|
||||
|
||||
clientPrivKey, clientCert, err := certificates.NewClient(caKey, caCert, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pemClientPrivKey, err := pem.EncodeKey(clientPrivKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pemClientCert, err := pem.EncodeCertificate(clientCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pemCaCert, err := pem.EncodeCertificate(caCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientSecretData := map[string]string{
|
||||
caCertKey: string(pemCaCert),
|
||||
clientPrivKeyKey: string(pemClientPrivKey),
|
||||
clientCertKey: string(pemClientCert),
|
||||
}
|
||||
|
||||
queryClientSecret, err := secret.AdaptFuncToEnsure(namespace, labels.AsSelectable(nameLabels), clientSecretData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queriers = append(queriers, operator.ResourceQueryToZitadelQuery(queryClientSecret))
|
||||
|
||||
return operator.QueriersToEnsureFunc(monitor, false, queriers, k8sClient, queried)
|
||||
}
|
||||
}, func(client string) operator.DestroyFunc {
|
||||
clientSecret := clientSecretPrefix + client
|
||||
|
||||
destroy, err := secret.AdaptFuncToDestroy(namespace, clientSecret)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return operator.ResourceDestroyToZitadelDestroy(destroy)
|
||||
},
|
||||
nil
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
kubernetesmock "github.com/caos/orbos/pkg/kubernetes/mock"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/core"
|
||||
coremock "github.com/caos/zitadel/operator/database/kinds/databases/core/mock"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate/pem"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
caPem = `-----BEGIN CERTIFICATE-----
|
||||
MIIE9TCCAt2gAwIBAgICB+MwDQYJKoZIhvcNAQELBQAwKzESMBAGA1UEChMJQ29j
|
||||
a3JvYWNoMRUwEwYDVQQDEwxDb2Nrcm9hY2ggQ0EwHhcNMjAxMjAxMTAyMjA0WhcN
|
||||
MzAxMjAxMTAyMjA0WjArMRIwEAYDVQQKEwlDb2Nrcm9hY2gxFTATBgNVBAMTDENv
|
||||
Y2tyb2FjaCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOja5IXJ
|
||||
GUY9sFgyvWkav+O74gzcv8y69uJzSOx0piOP+sfpZWVeGEjqO4JgdcS5NPMrT5Tb
|
||||
aJ52CjiOdlHVTyR87i5JmOIvA2qA4dWQmSbX7AQ8r9ptYDe9xMn+qFegcbr4YxCz
|
||||
K9mmbDZUhlLO7cz3QV6nvRxGFWbzffo8BXZnOUCAOyOHrbnPpLumnfZlL5BckdtY
|
||||
pS7jAlUpKSMBTK4AHcmrouFsNKHqlUopYXeJFdg9g1F0DuCnVP9x7+XcUW8dAVut
|
||||
Q7Jswy+++GAXs6mPVsYFLXUSYNyW+Bfl/jKwx0XTQx/6iyNpK0XtAzjBZFjXwmot
|
||||
0mODkqnfE3BB4lXxZ5knomAQEGSScUhCUb9upbF4uJJF27xr/kIkwtWxMGpCXds0
|
||||
IxI+wNRCenhfFZEIQCzri0zn6WdN8b/gbv1BErNcccYwolPUv1oUgYbzowbQ5O2D
|
||||
aLQPqO1VAZiZHLxb787bRywpCl33VZ1ptMHi2ogKjcsh4DQ9SsRj+rU/Tk5lyk7G
|
||||
FHteyHcq12TGpz9/CQYMacl8yeRRHfNO3Rq0jFTYeD4+ZdVBPKeuTHyXGzy2T157
|
||||
pgqMFzwqxlNYPzpuz7xsZBExJzCtcomB8fMlsCJnxV/kuMTTsWsPrRc7hsqzBCq3
|
||||
plfYT8a7EBCVmJmDu+6mMVh+A9M2zZCqtV57AgMBAAGjIzAhMA4GA1UdDwEB/wQE
|
||||
AwICpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQDRmkBYDk/F
|
||||
lYTgMaL7yhRAowDR8mtOl06Oba/MCDDsmBvfOVIqY9Sa9sJtb78Uvecv9WAE4qpb
|
||||
PNSaIvNmujmGQrBxtGwQdeHZvtXQ1lKAGBM+ukfz/NFU+6rwOdTuLgS5jTaiM4r1
|
||||
uMoIG6S5z/brWGHAw8Qv2Fq05dSLnevmPWK903V/RnwmCKT9as5Qpfkaie2PNory
|
||||
euxVGeGolxzgbSps3ZJlDSSymQBl10iJYYdGIsgcw5FHcCdpS4AutdpQbIFuCGk2
|
||||
CHTcTTa4KMaVfRm/gKm1gh8UnuVDLBKQS8+1CHFYUnih8ozBNUyBo5r5L4BHJK/n
|
||||
f0gnrYqaxtqPAyHkfo0PRc50HlAQRS/gW8ESv2PQmcSWlDggEzXt5MqFIYOfEXWE
|
||||
gtC7Ct1P7gzIRxolYVsNSgBR62sJM1PUa39E5v0QJsmuM51txHuw/PGqkkBlEwx9
|
||||
4u3FFXD9Pslg/g1l50Ek5O3/TWMta4Q+NagDDaOdkb4LRQr522ySgGIinBw00Xr+
|
||||
Ics2L42rP1LQyaE7+BljDEwmGaT7mrl8QGZR9uv/9mbtk2+q7wAOQzcMSFFACfwx
|
||||
KdKENmK0GpQZ6HaeDZO7wxGiAyFKhTNpd39XNOEhaKpWLSUxnZTLGlMIzvSyMGCj
|
||||
PV3xm+Xg1Xvki7f/qC1djysMcaokoJzNdg==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
caPrivPem = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
)
|
||||
|
||||
func TestClient_Adapt1(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
user := "test"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "cockroachdb")
|
||||
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
ca, err := pem.DecodeCertificate([]byte(caPem))
|
||||
assert.NoError(t, err)
|
||||
caPriv, err := pem.DecodeKey([]byte(caPrivPem))
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(ca)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(caPriv)
|
||||
k8sClient.EXPECT().ApplySecret(gomock.Any())
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
current := &tree.Tree{
|
||||
Parsed: dbCurrent,
|
||||
}
|
||||
core.SetQueriedForDatabase(queried, current)
|
||||
|
||||
createUser, _, err := AdaptFunc(monitor, namespace, componentLabels)
|
||||
assert.NoError(t, err)
|
||||
query := createUser(user)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestClient_Adapt2(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs2"
|
||||
user := "test2"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "cockroachdb")
|
||||
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
ca, err := pem.DecodeCertificate([]byte(caPem))
|
||||
assert.NoError(t, err)
|
||||
caPriv, err := pem.DecodeKey([]byte(caPrivPem))
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(ca)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(caPriv)
|
||||
k8sClient.EXPECT().ApplySecret(gomock.Any())
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
current := &tree.Tree{
|
||||
Parsed: dbCurrent,
|
||||
}
|
||||
core.SetQueriedForDatabase(queried, current)
|
||||
|
||||
createUser, _, err := AdaptFunc(monitor, namespace, componentLabels)
|
||||
assert.NoError(t, err)
|
||||
query := createUser(user)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
)
|
||||
|
||||
func QueryCertificates(
|
||||
namespace string,
|
||||
selector *labels.Selector,
|
||||
k8sClient kubernetes.ClientInt,
|
||||
) (
|
||||
[]string,
|
||||
error,
|
||||
) {
|
||||
|
||||
list, err := k8sClient.ListSecrets(namespace, labels.MustK8sMap(selector))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs := []string{}
|
||||
for _, secret := range list.Items {
|
||||
if strings.HasPrefix(secret.Name, clientSecretPrefix) {
|
||||
certs = append(certs, strings.TrimPrefix(secret.Name, clientSecretPrefix))
|
||||
}
|
||||
}
|
||||
|
||||
return certs, nil
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
kubernetesmock "github.com/caos/orbos/pkg/kubernetes/mock"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_Query0(t *testing.T) {
|
||||
namespace := "testNs"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "testKind", "v0"), "testComponent")
|
||||
clientLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ListSecrets(namespace, clientLabels).Times(1).Return(secretList, nil)
|
||||
|
||||
users, err := QueryCertificates(namespace, labels.DeriveComponentSelector(componentLabels, false), k8sClient)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, users, []string{})
|
||||
}
|
||||
|
||||
func TestClient_Query(t *testing.T) {
|
||||
namespace := "testNs"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "testKind", "v0"), "testComponent")
|
||||
clientLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: clientSecretPrefix + "test",
|
||||
},
|
||||
Data: map[string][]byte{},
|
||||
Type: "Opaque",
|
||||
}},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ListSecrets(namespace, clientLabels).Times(1).Return(secretList, nil)
|
||||
|
||||
users, err := QueryCertificates(namespace, labels.DeriveComponentSelector(componentLabels, false), k8sClient)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, users, "test")
|
||||
}
|
||||
|
||||
func TestClient_Query2(t *testing.T) {
|
||||
namespace := "testNs"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "testKind", "v0"), "testComponent")
|
||||
clientLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: clientSecretPrefix + "test1",
|
||||
},
|
||||
Data: map[string][]byte{},
|
||||
Type: "Opaque",
|
||||
}, {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: clientSecretPrefix + "test2",
|
||||
},
|
||||
Data: map[string][]byte{},
|
||||
Type: "Opaque",
|
||||
}},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ListSecrets(namespace, clientLabels).Times(1).Return(secretList, nil)
|
||||
|
||||
users, err := QueryCertificates(namespace, labels.DeriveComponentSelector(componentLabels, false), k8sClient)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, users, []string{"test1", "test2"})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
type Current struct {
|
||||
CertificateKey *rsa.PrivateKey
|
||||
Certificate []byte
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"github.com/caos/zitadel/operator"
|
||||
"reflect"
|
||||
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources/secret"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/core"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate/certificates"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate/pem"
|
||||
)
|
||||
|
||||
const (
|
||||
caCertKey = "ca.crt"
|
||||
caPrivKeyKey = "ca.key"
|
||||
nodeCertKey = "node.crt"
|
||||
nodePrivKeyKey = "node.key"
|
||||
)
|
||||
|
||||
func AdaptFunc(
|
||||
monitor mntr.Monitor,
|
||||
namespace string,
|
||||
nameLabels *labels.Name,
|
||||
clusterDns string,
|
||||
generateIfNotExists bool,
|
||||
) (
|
||||
operator.QueryFunc,
|
||||
operator.DestroyFunc,
|
||||
error,
|
||||
) {
|
||||
|
||||
caPrivKey := new(rsa.PrivateKey)
|
||||
caCert := make([]byte, 0)
|
||||
nodeSecretSelector := labels.MustK8sMap(labels.DeriveNameSelector(nameLabels, false))
|
||||
|
||||
return func(k8sClient kubernetes.ClientInt, queried map[string]interface{}) (operator.EnsureFunc, error) {
|
||||
queriers := make([]operator.QueryFunc, 0)
|
||||
|
||||
currentDB, err := core.ParseQueriedForDatabase(queried)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allNodeSecrets, err := k8sClient.ListSecrets(namespace, nodeSecretSelector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(allNodeSecrets.Items) == 0 {
|
||||
|
||||
if !generateIfNotExists {
|
||||
return nil, errors.New("node secret not found")
|
||||
}
|
||||
|
||||
emptyCert := true
|
||||
emptyKey := true
|
||||
if currentCaCert := currentDB.GetCertificate(); currentCaCert != nil && len(currentCaCert) != 0 {
|
||||
emptyCert = false
|
||||
caCert = currentCaCert
|
||||
}
|
||||
if currentCaCertKey := currentDB.GetCertificateKey(); currentCaCertKey != nil && !reflect.DeepEqual(currentCaCertKey, &rsa.PrivateKey{}) {
|
||||
emptyKey = false
|
||||
caPrivKey = currentCaCertKey
|
||||
}
|
||||
|
||||
if emptyCert || emptyKey {
|
||||
caPrivKeyInternal, caCertInternal, err := certificates.NewCA()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caPrivKey = caPrivKeyInternal
|
||||
caCert = caCertInternal
|
||||
|
||||
nodePrivKey, nodeCert, err := certificates.NewNode(caPrivKey, caCert, namespace, clusterDns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pemNodePrivKey, err := pem.EncodeKey(nodePrivKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pemCaPrivKey, err := pem.EncodeKey(caPrivKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pemCaCert, err := pem.EncodeCertificate(caCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pemNodeCert, err := pem.EncodeCertificate(nodeCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodeSecretData := map[string]string{
|
||||
caPrivKeyKey: string(pemCaPrivKey),
|
||||
caCertKey: string(pemCaCert),
|
||||
nodePrivKeyKey: string(pemNodePrivKey),
|
||||
nodeCertKey: string(pemNodeCert),
|
||||
}
|
||||
queryNodeSecret, err := secret.AdaptFuncToEnsure(namespace, labels.AsSelectable(nameLabels), nodeSecretData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queriers = append(queriers, operator.ResourceQueryToZitadelQuery(queryNodeSecret))
|
||||
}
|
||||
} else {
|
||||
key, err := pem.DecodeKey(allNodeSecrets.Items[0].Data[caPrivKeyKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caPrivKey = key
|
||||
|
||||
cert, err := pem.DecodeCertificate(allNodeSecrets.Items[0].Data[caCertKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCert = cert
|
||||
}
|
||||
|
||||
currentDB.SetCertificate(caCert)
|
||||
currentDB.SetCertificateKey(caPrivKey)
|
||||
|
||||
return operator.QueriersToEnsureFunc(monitor, false, queriers, k8sClient, queried)
|
||||
}, func(k8sClient kubernetes.ClientInt) error {
|
||||
allNodeSecrets, err := k8sClient.ListSecrets(namespace, nodeSecretSelector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, deleteSecret := range allNodeSecrets.Items {
|
||||
destroyer, err := secret.AdaptFuncToDestroy(namespace, deleteSecret.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := destroyer(k8sClient); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
kubernetesmock "github.com/caos/orbos/pkg/kubernetes/mock"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/core"
|
||||
coremock "github.com/caos/zitadel/operator/database/kinds/databases/core/mock"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate/pem"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
caPem = `-----BEGIN CERTIFICATE-----
|
||||
MIIE9TCCAt2gAwIBAgICB+MwDQYJKoZIhvcNAQELBQAwKzESMBAGA1UEChMJQ29j
|
||||
a3JvYWNoMRUwEwYDVQQDEwxDb2Nrcm9hY2ggQ0EwHhcNMjAxMjAxMTAyMjA0WhcN
|
||||
MzAxMjAxMTAyMjA0WjArMRIwEAYDVQQKEwlDb2Nrcm9hY2gxFTATBgNVBAMTDENv
|
||||
Y2tyb2FjaCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOja5IXJ
|
||||
GUY9sFgyvWkav+O74gzcv8y69uJzSOx0piOP+sfpZWVeGEjqO4JgdcS5NPMrT5Tb
|
||||
aJ52CjiOdlHVTyR87i5JmOIvA2qA4dWQmSbX7AQ8r9ptYDe9xMn+qFegcbr4YxCz
|
||||
K9mmbDZUhlLO7cz3QV6nvRxGFWbzffo8BXZnOUCAOyOHrbnPpLumnfZlL5BckdtY
|
||||
pS7jAlUpKSMBTK4AHcmrouFsNKHqlUopYXeJFdg9g1F0DuCnVP9x7+XcUW8dAVut
|
||||
Q7Jswy+++GAXs6mPVsYFLXUSYNyW+Bfl/jKwx0XTQx/6iyNpK0XtAzjBZFjXwmot
|
||||
0mODkqnfE3BB4lXxZ5knomAQEGSScUhCUb9upbF4uJJF27xr/kIkwtWxMGpCXds0
|
||||
IxI+wNRCenhfFZEIQCzri0zn6WdN8b/gbv1BErNcccYwolPUv1oUgYbzowbQ5O2D
|
||||
aLQPqO1VAZiZHLxb787bRywpCl33VZ1ptMHi2ogKjcsh4DQ9SsRj+rU/Tk5lyk7G
|
||||
FHteyHcq12TGpz9/CQYMacl8yeRRHfNO3Rq0jFTYeD4+ZdVBPKeuTHyXGzy2T157
|
||||
pgqMFzwqxlNYPzpuz7xsZBExJzCtcomB8fMlsCJnxV/kuMTTsWsPrRc7hsqzBCq3
|
||||
plfYT8a7EBCVmJmDu+6mMVh+A9M2zZCqtV57AgMBAAGjIzAhMA4GA1UdDwEB/wQE
|
||||
AwICpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQDRmkBYDk/F
|
||||
lYTgMaL7yhRAowDR8mtOl06Oba/MCDDsmBvfOVIqY9Sa9sJtb78Uvecv9WAE4qpb
|
||||
PNSaIvNmujmGQrBxtGwQdeHZvtXQ1lKAGBM+ukfz/NFU+6rwOdTuLgS5jTaiM4r1
|
||||
uMoIG6S5z/brWGHAw8Qv2Fq05dSLnevmPWK903V/RnwmCKT9as5Qpfkaie2PNory
|
||||
euxVGeGolxzgbSps3ZJlDSSymQBl10iJYYdGIsgcw5FHcCdpS4AutdpQbIFuCGk2
|
||||
CHTcTTa4KMaVfRm/gKm1gh8UnuVDLBKQS8+1CHFYUnih8ozBNUyBo5r5L4BHJK/n
|
||||
f0gnrYqaxtqPAyHkfo0PRc50HlAQRS/gW8ESv2PQmcSWlDggEzXt5MqFIYOfEXWE
|
||||
gtC7Ct1P7gzIRxolYVsNSgBR62sJM1PUa39E5v0QJsmuM51txHuw/PGqkkBlEwx9
|
||||
4u3FFXD9Pslg/g1l50Ek5O3/TWMta4Q+NagDDaOdkb4LRQr522ySgGIinBw00Xr+
|
||||
Ics2L42rP1LQyaE7+BljDEwmGaT7mrl8QGZR9uv/9mbtk2+q7wAOQzcMSFFACfwx
|
||||
KdKENmK0GpQZ6HaeDZO7wxGiAyFKhTNpd39XNOEhaKpWLSUxnZTLGlMIzvSyMGCj
|
||||
PV3xm+Xg1Xvki7f/qC1djysMcaokoJzNdg==
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
caPrivPem = `-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
)
|
||||
|
||||
func TestNode_AdaptWithCA(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
clusterDns := "testDns"
|
||||
nameLabels := labels.MustForName(labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "cockroachdb"), "testNode")
|
||||
|
||||
nodeLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "cockroachdb",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "testNode",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{},
|
||||
}
|
||||
ca, err := pem.DecodeCertificate([]byte(caPem))
|
||||
assert.NoError(t, err)
|
||||
caPriv, err := pem.DecodeKey([]byte(caPrivPem))
|
||||
assert.NoError(t, err)
|
||||
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(ca)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(caPriv)
|
||||
dbCurrent.EXPECT().SetCertificate(ca).Times(1)
|
||||
dbCurrent.EXPECT().SetCertificateKey(caPriv).Times(1)
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
current := &tree.Tree{
|
||||
Parsed: dbCurrent,
|
||||
}
|
||||
|
||||
core.SetQueriedForDatabase(queried, current)
|
||||
|
||||
query, _, err := AdaptFunc(monitor, namespace, nameLabels, clusterDns, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestNode_AdaptWithoutCA(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
clusterDns := "testDns"
|
||||
nameLabels := labels.MustForName(labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "cockroachdb"), "testNode")
|
||||
|
||||
nodeLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "cockroachdb",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "testNode",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
dbCurrent.EXPECT().GetCertificate().Times(1).Return(nil)
|
||||
dbCurrent.EXPECT().GetCertificateKey().Times(1).Return(nil)
|
||||
dbCurrent.EXPECT().SetCertificate(gomock.Any()).Times(1)
|
||||
dbCurrent.EXPECT().SetCertificateKey(gomock.Any()).Times(1)
|
||||
k8sClient.EXPECT().ApplySecret(gomock.Any()).Times(1).Return(nil)
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
current := &tree.Tree{
|
||||
Parsed: dbCurrent,
|
||||
}
|
||||
|
||||
core.SetQueriedForDatabase(queried, current)
|
||||
|
||||
query, _, err := AdaptFunc(monitor, namespace, nameLabels, clusterDns, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestNode_AdaptAlreadyExisting(t *testing.T) {
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
clusterDns := "testDns"
|
||||
nameLabels := labels.MustForName(labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "cockroachdb"), "testNode")
|
||||
nodeLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "cockroachdb",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": "testNode",
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
dbCurrent := coremock.NewMockDatabaseCurrent(gomock.NewController(t))
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
|
||||
secretList := &corev1.SecretList{
|
||||
Items: []corev1.Secret{{
|
||||
ObjectMeta: metav1.ObjectMeta{},
|
||||
Data: map[string][]byte{
|
||||
caCertKey: []byte(caPem),
|
||||
caPrivKeyKey: []byte(caPrivPem),
|
||||
},
|
||||
Type: "Opaque",
|
||||
}},
|
||||
}
|
||||
|
||||
caCert, err := pem.DecodeCertificate([]byte(caPem))
|
||||
assert.NoError(t, err)
|
||||
caPrivKey, err := pem.DecodeKey([]byte(caPrivPem))
|
||||
assert.NoError(t, err)
|
||||
|
||||
dbCurrent.EXPECT().SetCertificate(caCert).Times(1)
|
||||
dbCurrent.EXPECT().SetCertificateKey(caPrivKey).Times(1)
|
||||
k8sClient.EXPECT().ListSecrets(namespace, nodeLabels).Times(1).Return(secretList, nil)
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
current := &tree.Tree{
|
||||
Parsed: dbCurrent,
|
||||
}
|
||||
|
||||
core.SetQueriedForDatabase(queried, current)
|
||||
|
||||
query, _, err := AdaptFunc(monitor, namespace, nameLabels, clusterDns, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package pem
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func EncodeCertificate(data []byte) ([]byte, error) {
|
||||
certPem := new(bytes.Buffer)
|
||||
if err := pem.Encode(certPem, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: data,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return certPem.Bytes(), nil
|
||||
}
|
||||
|
||||
func EncodeKey(key *rsa.PrivateKey) ([]byte, error) {
|
||||
keyPem := new(bytes.Buffer)
|
||||
if err := pem.Encode(keyPem, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keyPem.Bytes(), nil
|
||||
}
|
||||
|
||||
func DecodeKey(data []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil || block.Type != "RSA PRIVATE KEY" {
|
||||
return nil, errors.New("failed to decode PEM block containing public key")
|
||||
}
|
||||
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
}
|
||||
|
||||
func DecodeCertificate(data []byte) ([]byte, error) {
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
return nil, errors.New("failed to decode PEM block containing public key")
|
||||
}
|
||||
return block.Bytes, nil
|
||||
}
|
||||
73
operator/database/kinds/databases/managed/current.go
Normal file
73
operator/database/kinds/databases/managed/current.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package managed
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate"
|
||||
)
|
||||
|
||||
type Current struct {
|
||||
Common *tree.Common `yaml:",inline"`
|
||||
Current *CurrentDB
|
||||
}
|
||||
|
||||
type CurrentDB struct {
|
||||
URL string
|
||||
Port string
|
||||
ReadyFunc operator.EnsureFunc
|
||||
CA *certificate.Current
|
||||
AddUserFunc func(user string) (operator.QueryFunc, error)
|
||||
DeleteUserFunc func(user string) (operator.DestroyFunc, error)
|
||||
ListUsersFunc func(k8sClient kubernetes.ClientInt) ([]string, error)
|
||||
ListDatabasesFunc func(k8sClient kubernetes.ClientInt) ([]string, error)
|
||||
}
|
||||
|
||||
func (c *Current) GetURL() string {
|
||||
return c.Current.URL
|
||||
}
|
||||
|
||||
func (c *Current) GetPort() string {
|
||||
return c.Current.Port
|
||||
}
|
||||
|
||||
func (c *Current) GetReadyQuery() operator.EnsureFunc {
|
||||
return c.Current.ReadyFunc
|
||||
}
|
||||
|
||||
func (c *Current) GetCA() *certificate.Current {
|
||||
return c.Current.CA
|
||||
}
|
||||
|
||||
func (c *Current) GetCertificateKey() *rsa.PrivateKey {
|
||||
return c.Current.CA.CertificateKey
|
||||
}
|
||||
|
||||
func (c *Current) SetCertificateKey(key *rsa.PrivateKey) {
|
||||
c.Current.CA.CertificateKey = key
|
||||
}
|
||||
|
||||
func (c *Current) GetCertificate() []byte {
|
||||
return c.Current.CA.Certificate
|
||||
}
|
||||
|
||||
func (c *Current) SetCertificate(cert []byte) {
|
||||
c.Current.CA.Certificate = cert
|
||||
}
|
||||
|
||||
func (c *Current) GetListDatabasesFunc() func(k8sClient kubernetes.ClientInt) ([]string, error) {
|
||||
return c.Current.ListDatabasesFunc
|
||||
}
|
||||
|
||||
func (c *Current) GetListUsersFunc() func(k8sClient kubernetes.ClientInt) ([]string, error) {
|
||||
return c.Current.ListUsersFunc
|
||||
}
|
||||
|
||||
func (c *Current) GetAddUserFunc() func(user string) (operator.QueryFunc, error) {
|
||||
return c.Current.AddUserFunc
|
||||
}
|
||||
|
||||
func (c *Current) GetDeleteUserFunc() func(user string) (operator.DestroyFunc, error) {
|
||||
return c.Current.DeleteUserFunc
|
||||
}
|
||||
50
operator/database/kinds/databases/managed/database/adapt.go
Normal file
50
operator/database/kinds/databases/managed/database/adapt.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/caos/zitadel/operator"
|
||||
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
)
|
||||
|
||||
func AdaptFunc(
|
||||
monitor mntr.Monitor,
|
||||
namespace string,
|
||||
deployName string,
|
||||
containerName string,
|
||||
certsDir string,
|
||||
userName string,
|
||||
) (
|
||||
operator.QueryFunc,
|
||||
operator.DestroyFunc,
|
||||
error,
|
||||
) {
|
||||
cmdSql := fmt.Sprintf("cockroach sql --certs-dir=%s", certsDir)
|
||||
|
||||
createSql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s ", userName)
|
||||
|
||||
deleteSql := fmt.Sprintf("DROP DATABASE IF EXISTS %s", userName)
|
||||
|
||||
ensureDatabase := func(k8sClient kubernetes.ClientInt) error {
|
||||
return k8sClient.ExecInPodOfDeployment(namespace, deployName, containerName, fmt.Sprintf("%s -e '%s;'", cmdSql, createSql))
|
||||
}
|
||||
|
||||
destroyDatabase := func(k8sClient kubernetes.ClientInt) error {
|
||||
return k8sClient.ExecInPodOfDeployment(namespace, deployName, containerName, fmt.Sprintf("%s -e '%s;'", cmdSql, deleteSql))
|
||||
}
|
||||
|
||||
queriers := []operator.QueryFunc{
|
||||
operator.EnsureFuncToQueryFunc(ensureDatabase),
|
||||
}
|
||||
|
||||
destroyers := []operator.DestroyFunc{
|
||||
destroyDatabase,
|
||||
}
|
||||
|
||||
return func(k8sClient kubernetes.ClientInt, queried map[string]interface{}) (operator.EnsureFunc, error) {
|
||||
return operator.QueriersToEnsureFunc(monitor, false, queriers, k8sClient, queried)
|
||||
},
|
||||
operator.DestroyersToDestroyFunc(monitor, destroyers),
|
||||
nil
|
||||
}
|
||||
39
operator/database/kinds/databases/managed/desired.go
Normal file
39
operator/database/kinds/databases/managed/desired.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package managed
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/pkg/kubernetes/k8s"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
type DesiredV0 struct {
|
||||
Common *tree.Common `yaml:",inline"`
|
||||
Spec Spec
|
||||
}
|
||||
|
||||
type Spec struct {
|
||||
Verbose bool
|
||||
Force bool `yaml:"force,omitempty"`
|
||||
ReplicaCount int `yaml:"replicaCount,omitempty"`
|
||||
StorageCapacity string `yaml:"storageCapacity,omitempty"`
|
||||
StorageClass string `yaml:"storageClass,omitempty"`
|
||||
NodeSelector map[string]string `yaml:"nodeSelector,omitempty"`
|
||||
Tolerations []corev1.Toleration `yaml:"tolerations,omitempty"`
|
||||
ClusterDns string `yaml:"clusterDNS,omitempty"`
|
||||
Backups map[string]*tree.Tree `yaml:"backups,omitempty"`
|
||||
Resources *k8s.Resources `yaml:"resources,omitempty"`
|
||||
}
|
||||
|
||||
func parseDesiredV0(desiredTree *tree.Tree) (*DesiredV0, error) {
|
||||
desiredKind := &DesiredV0{
|
||||
Common: desiredTree.Common,
|
||||
Spec: Spec{},
|
||||
}
|
||||
|
||||
if err := desiredTree.Original.Decode(desiredKind); err != nil {
|
||||
return nil, errors.Wrap(err, "parsing desired state failed")
|
||||
}
|
||||
|
||||
return desiredKind, nil
|
||||
}
|
||||
36
operator/database/kinds/databases/managed/listbackups.go
Normal file
36
operator/database/kinds/databases/managed/listbackups.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package managed
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator/database/kinds/backups"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func BackupList() func(monitor mntr.Monitor, desired *tree.Tree) ([]string, error) {
|
||||
return func(monitor mntr.Monitor, desired *tree.Tree) ([]string, error) {
|
||||
desiredKind, err := parseDesiredV0(desired)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parsing desired state failed")
|
||||
}
|
||||
desired.Parsed = desiredKind
|
||||
|
||||
if !monitor.IsVerbose() && desiredKind.Spec.Verbose {
|
||||
monitor.Verbose()
|
||||
}
|
||||
|
||||
backuplists := make([]string, 0)
|
||||
if desiredKind.Spec.Backups != nil {
|
||||
for name, def := range desiredKind.Spec.Backups {
|
||||
backuplist, err := backups.GetBackupList(monitor, name, def)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, backup := range backuplist {
|
||||
backuplists = append(backuplists, name+"."+backup)
|
||||
}
|
||||
}
|
||||
}
|
||||
return backuplists, nil
|
||||
}
|
||||
}
|
||||
106
operator/database/kinds/databases/managed/rbac/adapt.go
Normal file
106
operator/database/kinds/databases/managed/rbac/adapt.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources/clusterrole"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources/clusterrolebinding"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources/role"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources/rolebinding"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources/serviceaccount"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/zitadel/operator"
|
||||
)
|
||||
|
||||
func AdaptFunc(
|
||||
monitor mntr.Monitor,
|
||||
namespace string,
|
||||
nameLabels *labels.Name,
|
||||
) (
|
||||
operator.QueryFunc,
|
||||
operator.DestroyFunc,
|
||||
error,
|
||||
) {
|
||||
|
||||
internalMonitor := monitor.WithField("component", "rbac")
|
||||
|
||||
serviceAccountLabels := nameLabels
|
||||
roleLabels := nameLabels
|
||||
clusterRoleLabels := nameLabels
|
||||
|
||||
destroySA, err := serviceaccount.AdaptFuncToDestroy(namespace, serviceAccountLabels.Name())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
destroyR, err := role.AdaptFuncToDestroy(namespace, roleLabels.Name())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
destroyCR, err := clusterrole.AdaptFuncToDestroy(clusterRoleLabels.Name())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
destroyRB, err := rolebinding.AdaptFuncToDestroy(namespace, roleLabels.Name())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
destroyCRB, err := clusterrolebinding.AdaptFuncToDestroy(roleLabels.Name())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
destroyers := []operator.DestroyFunc{
|
||||
operator.ResourceDestroyToZitadelDestroy(destroyR),
|
||||
operator.ResourceDestroyToZitadelDestroy(destroyCR),
|
||||
operator.ResourceDestroyToZitadelDestroy(destroyRB),
|
||||
operator.ResourceDestroyToZitadelDestroy(destroyCRB),
|
||||
operator.ResourceDestroyToZitadelDestroy(destroySA),
|
||||
}
|
||||
|
||||
querySA, err := serviceaccount.AdaptFuncToEnsure(namespace, serviceAccountLabels)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
queryR, err := role.AdaptFuncToEnsure(namespace, roleLabels, []string{""}, []string{"secrets"}, []string{"create", "get"})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
queryCR, err := clusterrole.AdaptFuncToEnsure(clusterRoleLabels, []string{"certificates.k8s.io"}, []string{"certificatesigningrequests"}, []string{"create", "get", "watch"})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
subjects := []rolebinding.Subject{{Kind: "ServiceAccount", Name: serviceAccountLabels.Name(), Namespace: namespace}}
|
||||
queryRB, err := rolebinding.AdaptFuncToEnsure(namespace, roleLabels, subjects, roleLabels.Name())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
subjectsCRB := []clusterrolebinding.Subject{{Kind: "ServiceAccount", Name: serviceAccountLabels.Name(), Namespace: namespace}}
|
||||
queryCRB, err := clusterrolebinding.AdaptFuncToEnsure(roleLabels, subjectsCRB, roleLabels.Name())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
queriers := []operator.QueryFunc{
|
||||
//serviceaccount
|
||||
operator.ResourceQueryToZitadelQuery(querySA),
|
||||
//rbac
|
||||
operator.ResourceQueryToZitadelQuery(queryR),
|
||||
operator.ResourceQueryToZitadelQuery(queryCR),
|
||||
operator.ResourceQueryToZitadelQuery(queryRB),
|
||||
operator.ResourceQueryToZitadelQuery(queryCRB),
|
||||
}
|
||||
return func(k8sClient kubernetes.ClientInt, queried map[string]interface{}) (operator.EnsureFunc, error) {
|
||||
return operator.QueriersToEnsureFunc(internalMonitor, false, queriers, k8sClient, queried)
|
||||
},
|
||||
operator.DestroyersToDestroyFunc(internalMonitor, destroyers),
|
||||
nil
|
||||
|
||||
}
|
||||
207
operator/database/kinds/databases/managed/rbac/adapt_test.go
Normal file
207
operator/database/kinds/databases/managed/rbac/adapt_test.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
kubernetesmock "github.com/caos/orbos/pkg/kubernetes/mock"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRbac_Adapt1(t *testing.T) {
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
name := "testName"
|
||||
k8sLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": name,
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"app.kubernetes.io/version": "testVersion",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "cockroachdb",
|
||||
}
|
||||
nameLabels := labels.MustForName(labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "testComponent"), name)
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
|
||||
k8sClient.EXPECT().ApplyServiceAccount(&corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sLabels,
|
||||
}})
|
||||
|
||||
k8sClient.EXPECT().ApplyRole(&rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sLabels,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"secrets"},
|
||||
Verbs: []string{"create", "get"},
|
||||
},
|
||||
},
|
||||
})
|
||||
k8sClient.EXPECT().ApplyClusterRole(&rbacv1.ClusterRole{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: k8sLabels,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{"certificates.k8s.io"},
|
||||
Resources: []string{"certificatesigningrequests"},
|
||||
Verbs: []string{"create", "get", "watch"},
|
||||
},
|
||||
},
|
||||
})
|
||||
k8sClient.EXPECT().ApplyRoleBinding(&rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sLabels,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{{
|
||||
Kind: "ServiceAccount",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
}},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
Name: name,
|
||||
Kind: "Role",
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
},
|
||||
})
|
||||
|
||||
k8sClient.EXPECT().ApplyClusterRoleBinding(&rbacv1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: k8sLabels,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{{
|
||||
Kind: "ServiceAccount",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
}},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Name: name,
|
||||
Kind: "ClusterRole",
|
||||
},
|
||||
})
|
||||
|
||||
query, _, err := AdaptFunc(monitor, namespace, nameLabels)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestRbac_Adapt2(t *testing.T) {
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs2"
|
||||
name := "testName2"
|
||||
k8sLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent2",
|
||||
"app.kubernetes.io/managed-by": "testOp2",
|
||||
"app.kubernetes.io/name": name,
|
||||
"app.kubernetes.io/part-of": "testProd2",
|
||||
"app.kubernetes.io/version": "testVersion2",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "cockroachdb",
|
||||
}
|
||||
nameLabels := labels.MustForName(labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd2", "testOp2", "testVersion2"), "cockroachdb", "v0"), "testComponent2"), name)
|
||||
|
||||
queried := map[string]interface{}{}
|
||||
|
||||
k8sClient.EXPECT().ApplyServiceAccount(&corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sLabels,
|
||||
}})
|
||||
|
||||
k8sClient.EXPECT().ApplyRole(&rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sLabels,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"secrets"},
|
||||
Verbs: []string{"create", "get"},
|
||||
},
|
||||
},
|
||||
})
|
||||
k8sClient.EXPECT().ApplyClusterRole(&rbacv1.ClusterRole{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: k8sLabels,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{"certificates.k8s.io"},
|
||||
Resources: []string{"certificatesigningrequests"},
|
||||
Verbs: []string{"create", "get", "watch"},
|
||||
},
|
||||
},
|
||||
})
|
||||
k8sClient.EXPECT().ApplyRoleBinding(&rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sLabels,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{{
|
||||
Kind: "ServiceAccount",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
}},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
Name: name,
|
||||
Kind: "Role",
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
},
|
||||
})
|
||||
|
||||
k8sClient.EXPECT().ApplyClusterRoleBinding(&rbacv1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Labels: k8sLabels,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{{
|
||||
Kind: "ServiceAccount",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
}},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Name: name,
|
||||
Kind: "ClusterRole",
|
||||
},
|
||||
})
|
||||
|
||||
query, _, err := AdaptFunc(monitor, namespace, nameLabels)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
79
operator/database/kinds/databases/managed/services/adapt.go
Normal file
79
operator/database/kinds/databases/managed/services/adapt.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/operator"
|
||||
"strconv"
|
||||
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources/service"
|
||||
)
|
||||
|
||||
func AdaptFunc(
|
||||
monitor mntr.Monitor,
|
||||
namespace string,
|
||||
publicServiceNameLabels *labels.Name,
|
||||
privateServiceNameLabels *labels.Name,
|
||||
cockroachSelector *labels.Selector,
|
||||
cockroachPort int32,
|
||||
cockroachHTTPPort int32,
|
||||
) (
|
||||
operator.QueryFunc,
|
||||
operator.DestroyFunc,
|
||||
error,
|
||||
) {
|
||||
internalMonitor := monitor.WithField("type", "services")
|
||||
|
||||
publicServiceSelectable := labels.AsSelectable(publicServiceNameLabels)
|
||||
|
||||
destroySPD, err := service.AdaptFuncToDestroy("default", publicServiceSelectable.Name())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
destroySP, err := service.AdaptFuncToDestroy(namespace, publicServiceSelectable.Name())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
destroyS, err := service.AdaptFuncToDestroy(namespace, privateServiceNameLabels.Name())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
destroyers := []operator.DestroyFunc{
|
||||
operator.ResourceDestroyToZitadelDestroy(destroySPD),
|
||||
operator.ResourceDestroyToZitadelDestroy(destroySP),
|
||||
operator.ResourceDestroyToZitadelDestroy(destroyS),
|
||||
}
|
||||
|
||||
ports := []service.Port{
|
||||
{Port: 26257, TargetPort: strconv.Itoa(int(cockroachPort)), Name: "grpc"},
|
||||
{Port: 8080, TargetPort: strconv.Itoa(int(cockroachHTTPPort)), Name: "http"},
|
||||
}
|
||||
querySPD, err := service.AdaptFuncToEnsure("default", publicServiceSelectable, ports, "", cockroachSelector, false, "", "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
querySP, err := service.AdaptFuncToEnsure(namespace, publicServiceSelectable, ports, "", cockroachSelector, false, "", "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
queryS, err := service.AdaptFuncToEnsure(namespace, privateServiceNameLabels, ports, "", cockroachSelector, true, "None", "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
queriers := []operator.QueryFunc{
|
||||
operator.ResourceQueryToZitadelQuery(querySPD),
|
||||
operator.ResourceQueryToZitadelQuery(querySP),
|
||||
operator.ResourceQueryToZitadelQuery(queryS),
|
||||
}
|
||||
|
||||
return func(k8sClient kubernetes.ClientInt, queried map[string]interface{}) (operator.EnsureFunc, error) {
|
||||
|
||||
return operator.QueriersToEnsureFunc(internalMonitor, false, queriers, k8sClient, queried)
|
||||
},
|
||||
operator.DestroyersToDestroyFunc(internalMonitor, destroyers),
|
||||
nil
|
||||
|
||||
}
|
||||
218
operator/database/kinds/databases/managed/services/adapt_test.go
Normal file
218
operator/database/kinds/databases/managed/services/adapt_test.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
kubernetesmock "github.com/caos/orbos/pkg/kubernetes/mock"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestService_Adapt1(t *testing.T) {
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "testComponent")
|
||||
|
||||
name := "testSvc"
|
||||
k8sLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": name,
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"app.kubernetes.io/version": "testVersion",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "cockroachdb",
|
||||
}
|
||||
nameLabels := labels.MustForName(componentLabels, name)
|
||||
publicName := "testPublic"
|
||||
k8sPublicLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": publicName,
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"app.kubernetes.io/version": "testVersion",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "cockroachdb",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
publicNameLabels := labels.MustForName(componentLabels, publicName)
|
||||
|
||||
cdbName := "testCdbName"
|
||||
k8sCdbLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": cdbName,
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
cdbNameLabels := labels.MustForName(componentLabels, cdbName)
|
||||
|
||||
cockroachPort := int32(25267)
|
||||
cockroachHttpPort := int32(8080)
|
||||
queried := map[string]interface{}{}
|
||||
|
||||
k8sClient.EXPECT().ApplyService(&corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: publicName,
|
||||
Namespace: namespace,
|
||||
Labels: k8sPublicLabels,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: []corev1.ServicePort{
|
||||
{Port: 26257, TargetPort: intstr.FromInt(int(cockroachPort)), Name: "grpc"},
|
||||
{Port: 8080, TargetPort: intstr.FromInt(int(cockroachHttpPort)), Name: "http"},
|
||||
},
|
||||
Selector: k8sCdbLabels,
|
||||
PublishNotReadyAddresses: false,
|
||||
},
|
||||
})
|
||||
|
||||
k8sClient.EXPECT().ApplyService(&corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: publicName,
|
||||
Namespace: "default",
|
||||
Labels: k8sPublicLabels,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: []corev1.ServicePort{
|
||||
{Port: 26257, TargetPort: intstr.FromInt(int(cockroachPort)), Name: "grpc"},
|
||||
{Port: 8080, TargetPort: intstr.FromInt(int(cockroachHttpPort)), Name: "http"},
|
||||
},
|
||||
Selector: k8sCdbLabels,
|
||||
PublishNotReadyAddresses: false,
|
||||
},
|
||||
})
|
||||
|
||||
k8sClient.EXPECT().ApplyService(&corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sLabels,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: []corev1.ServicePort{
|
||||
{Port: 26257, TargetPort: intstr.FromInt(int(cockroachPort)), Name: "grpc"},
|
||||
{Port: 8080, TargetPort: intstr.FromInt(int(cockroachHttpPort)), Name: "http"},
|
||||
},
|
||||
Selector: k8sCdbLabels,
|
||||
PublishNotReadyAddresses: true,
|
||||
ClusterIP: "None",
|
||||
},
|
||||
})
|
||||
|
||||
query, _, err := AdaptFunc(monitor, namespace, publicNameLabels, nameLabels, labels.DeriveNameSelector(cdbNameLabels, false), cockroachPort, cockroachHttpPort)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestService_Adapt2(t *testing.T) {
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs2"
|
||||
componentLabels := labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd2", "testOp2", "testVersion2"), "cockroachdb", "v0"), "testComponent2")
|
||||
|
||||
name := "testSvc2"
|
||||
k8sLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent2",
|
||||
"app.kubernetes.io/managed-by": "testOp2",
|
||||
"app.kubernetes.io/name": name,
|
||||
"app.kubernetes.io/part-of": "testProd2",
|
||||
"app.kubernetes.io/version": "testVersion2",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "cockroachdb",
|
||||
}
|
||||
nameLabels := labels.MustForName(componentLabels, name)
|
||||
publicName := "testPublic2"
|
||||
k8sPublicLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent2",
|
||||
"app.kubernetes.io/managed-by": "testOp2",
|
||||
"app.kubernetes.io/name": publicName,
|
||||
"app.kubernetes.io/part-of": "testProd2",
|
||||
"app.kubernetes.io/version": "testVersion2",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "cockroachdb",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
publicNameLabels := labels.MustForName(componentLabels, publicName)
|
||||
|
||||
cdbName := "testCdbName2"
|
||||
k8sCdbLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent2",
|
||||
"app.kubernetes.io/managed-by": "testOp2",
|
||||
"app.kubernetes.io/name": cdbName,
|
||||
"app.kubernetes.io/part-of": "testProd2",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
cdbNameLabels := labels.MustForName(componentLabels, cdbName)
|
||||
cockroachPort := int32(23)
|
||||
cockroachHttpPort := int32(24)
|
||||
queried := map[string]interface{}{}
|
||||
|
||||
k8sClient.EXPECT().ApplyService(&corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: publicName,
|
||||
Namespace: namespace,
|
||||
Labels: k8sPublicLabels,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: []corev1.ServicePort{
|
||||
{Port: 26257, TargetPort: intstr.FromInt(int(cockroachPort)), Name: "grpc"},
|
||||
{Port: 8080, TargetPort: intstr.FromInt(int(cockroachHttpPort)), Name: "http"},
|
||||
},
|
||||
Selector: k8sCdbLabels,
|
||||
PublishNotReadyAddresses: false,
|
||||
},
|
||||
})
|
||||
|
||||
k8sClient.EXPECT().ApplyService(&corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: publicName,
|
||||
Namespace: "default",
|
||||
Labels: k8sPublicLabels,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: []corev1.ServicePort{
|
||||
{Port: 26257, TargetPort: intstr.FromInt(int(cockroachPort)), Name: "grpc"},
|
||||
{Port: 8080, TargetPort: intstr.FromInt(int(cockroachHttpPort)), Name: "http"},
|
||||
},
|
||||
Selector: k8sCdbLabels,
|
||||
PublishNotReadyAddresses: false,
|
||||
},
|
||||
})
|
||||
|
||||
k8sClient.EXPECT().ApplyService(&corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sLabels,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: []corev1.ServicePort{
|
||||
{Port: 26257, TargetPort: intstr.FromInt(int(cockroachPort)), Name: "grpc"},
|
||||
{Port: 8080, TargetPort: intstr.FromInt(int(cockroachHttpPort)), Name: "http"},
|
||||
},
|
||||
Selector: k8sCdbLabels,
|
||||
PublishNotReadyAddresses: true,
|
||||
ClusterIP: "None",
|
||||
},
|
||||
})
|
||||
|
||||
query, _, err := AdaptFunc(monitor, namespace, publicNameLabels, nameLabels, labels.DeriveNameSelector(cdbNameLabels, false), cockroachPort, cockroachHttpPort)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient, queried)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
352
operator/database/kinds/databases/managed/statefulset/adapt.go
Normal file
352
operator/database/kinds/databases/managed/statefulset/adapt.go
Normal file
@@ -0,0 +1,352 @@
|
||||
package statefulset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/zitadel/operator"
|
||||
"github.com/caos/zitadel/operator/helpers"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/kubernetes/k8s"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources"
|
||||
"github.com/caos/orbos/pkg/kubernetes/resources/statefulset"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
certPath = "/cockroach/cockroach-certs"
|
||||
clientCertPath = "/cockroach/cockroach-client-certs"
|
||||
datadirPath = "/cockroach/cockroach-data"
|
||||
datadirInternal = "datadir"
|
||||
certsInternal = "certs"
|
||||
clientCertsInternal = "client-certs"
|
||||
defaultMode = int32(256)
|
||||
nodeSecret = "cockroachdb.node"
|
||||
rootSecret = "cockroachdb.client.root"
|
||||
)
|
||||
|
||||
type Affinity struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
type Affinitys []metav1.LabelSelectorRequirement
|
||||
|
||||
func (a Affinitys) Len() int { return len(a) }
|
||||
func (a Affinitys) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a Affinitys) Less(i, j int) bool { return a[i].Key < a[j].Key }
|
||||
|
||||
func AdaptFunc(
|
||||
monitor mntr.Monitor,
|
||||
sfsSelectable *labels.Selectable,
|
||||
podSelector *labels.Selector,
|
||||
force bool,
|
||||
namespace string,
|
||||
image string,
|
||||
serviceAccountName string,
|
||||
replicaCount int,
|
||||
storageCapacity string,
|
||||
dbPort int32,
|
||||
httpPort int32,
|
||||
storageClass string,
|
||||
nodeSelector map[string]string,
|
||||
tolerations []corev1.Toleration,
|
||||
resourcesSFS *k8s.Resources,
|
||||
) (
|
||||
resources.QueryFunc,
|
||||
resources.DestroyFunc,
|
||||
operator.EnsureFunc,
|
||||
operator.EnsureFunc,
|
||||
func(k8sClient kubernetes.ClientInt) ([]string, error),
|
||||
error,
|
||||
) {
|
||||
internalMonitor := monitor.WithField("component", "statefulset")
|
||||
|
||||
quantity, err := resource.ParseQuantity(storageCapacity)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
name := sfsSelectable.Name()
|
||||
k8sSelectable := labels.MustK8sMap(sfsSelectable)
|
||||
statefulsetDef := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sSelectable,
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
ServiceName: name,
|
||||
Replicas: helpers.PointerInt32(int32(replicaCount)),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: labels.MustK8sMap(podSelector),
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: k8sSelectable,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
NodeSelector: nodeSelector,
|
||||
Tolerations: tolerations,
|
||||
ServiceAccountName: serviceAccountName,
|
||||
Affinity: getAffinity(k8sSelectable),
|
||||
Containers: []corev1.Container{{
|
||||
Name: name,
|
||||
Image: image,
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
Ports: []corev1.ContainerPort{
|
||||
{ContainerPort: dbPort, Name: "grpc"},
|
||||
{ContainerPort: httpPort, Name: "http"},
|
||||
},
|
||||
LivenessProbe: &corev1.Probe{
|
||||
Handler: corev1.Handler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/health",
|
||||
Port: intstr.Parse("http"),
|
||||
Scheme: "HTTPS",
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 30,
|
||||
PeriodSeconds: 5,
|
||||
},
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
Handler: corev1.Handler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/health?ready=1",
|
||||
Port: intstr.Parse("http"),
|
||||
Scheme: "HTTPS",
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 10,
|
||||
PeriodSeconds: 5,
|
||||
FailureThreshold: 2,
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{{
|
||||
Name: datadirInternal,
|
||||
MountPath: datadirPath,
|
||||
}, {
|
||||
Name: certsInternal,
|
||||
MountPath: certPath,
|
||||
}, {
|
||||
Name: clientCertsInternal,
|
||||
MountPath: clientCertPath,
|
||||
}},
|
||||
Env: []corev1.EnvVar{{
|
||||
Name: "COCKROACH_CHANNEL",
|
||||
Value: "kubernetes-multiregion",
|
||||
}},
|
||||
Command: []string{
|
||||
"/bin/bash",
|
||||
"-ecx",
|
||||
getJoinExec(
|
||||
namespace,
|
||||
name,
|
||||
int(dbPort),
|
||||
replicaCount,
|
||||
),
|
||||
},
|
||||
Resources: getResources(resourcesSFS),
|
||||
}},
|
||||
Volumes: []corev1.Volume{{
|
||||
Name: datadirInternal,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: datadirInternal,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: certsInternal,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: nodeSecret,
|
||||
DefaultMode: helpers.PointerInt32(defaultMode),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: clientCertsInternal,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: rootSecret,
|
||||
DefaultMode: helpers.PointerInt32(defaultMode),
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
PodManagementPolicy: appsv1.ParallelPodManagement,
|
||||
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
|
||||
Type: "RollingUpdate",
|
||||
},
|
||||
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: datadirInternal,
|
||||
},
|
||||
Spec: corev1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []corev1.PersistentVolumeAccessMode{
|
||||
corev1.PersistentVolumeAccessMode("ReadWriteOnce"),
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
"storage": quantity,
|
||||
},
|
||||
},
|
||||
StorageClassName: &storageClass,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
query, err := statefulset.AdaptFuncToEnsure(statefulsetDef, force)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
destroy, err := statefulset.AdaptFuncToDestroy(namespace, name)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
wrapedQuery, wrapedDestroy, err := resources.WrapFuncs(internalMonitor, query, destroy)
|
||||
checkDBRunning := func(k8sClient kubernetes.ClientInt) error {
|
||||
internalMonitor.Info("waiting for statefulset to be running")
|
||||
if err := k8sClient.WaitUntilStatefulsetIsReady(namespace, name, true, false, 60); err != nil {
|
||||
internalMonitor.Error(errors.Wrap(err, "error while waiting for statefulset to be running"))
|
||||
return err
|
||||
}
|
||||
internalMonitor.Info("statefulset is running")
|
||||
return nil
|
||||
}
|
||||
|
||||
checkDBNotReady := func(k8sClient kubernetes.ClientInt) error {
|
||||
internalMonitor.Info("checking for statefulset to not be ready")
|
||||
if err := k8sClient.WaitUntilStatefulsetIsReady(namespace, name, true, true, 1); err != nil {
|
||||
internalMonitor.Info("statefulset is not ready")
|
||||
return nil
|
||||
}
|
||||
internalMonitor.Info("statefulset is ready")
|
||||
return errors.New("statefulset is ready")
|
||||
}
|
||||
|
||||
ensureInit := func(k8sClient kubernetes.ClientInt) error {
|
||||
if err := checkDBRunning(k8sClient); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkDBNotReady(k8sClient); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
command := "/cockroach/cockroach init --certs-dir=" + clientCertPath + " --host=" + name + "-0." + name
|
||||
|
||||
if err := k8sClient.ExecInPod(namespace, name+"-0", name, command); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
checkDBReady := func(k8sClient kubernetes.ClientInt) error {
|
||||
internalMonitor.Info("waiting for statefulset to be ready")
|
||||
if err := k8sClient.WaitUntilStatefulsetIsReady(namespace, name, true, true, 60); err != nil {
|
||||
internalMonitor.Error(errors.Wrap(err, "error while waiting for statefulset to be ready"))
|
||||
return err
|
||||
}
|
||||
internalMonitor.Info("statefulset is ready")
|
||||
return nil
|
||||
}
|
||||
|
||||
getAllDBs := func(k8sClient kubernetes.ClientInt) ([]string, error) {
|
||||
if err := checkDBRunning(k8sClient); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := checkDBReady(k8sClient); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
command := "/cockroach/cockroach sql --certs-dir=" + clientCertPath + " --host=" + name + "-0." + name + " -e 'SHOW DATABASES;'"
|
||||
|
||||
databasesStr, err := k8sClient.ExecInPodWithOutput(namespace, name+"-0", name, command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
databases := strings.Split(databasesStr, "\n")
|
||||
dbAndOwners := databases[1 : len(databases)-1]
|
||||
dbs := []string{}
|
||||
for _, dbAndOwner := range dbAndOwners {
|
||||
parts := strings.Split(dbAndOwner, "\t")
|
||||
if parts[1] != "node" {
|
||||
dbs = append(dbs, parts[0])
|
||||
}
|
||||
}
|
||||
return dbs, nil
|
||||
}
|
||||
|
||||
return wrapedQuery, wrapedDestroy, ensureInit, checkDBReady, getAllDBs, err
|
||||
}
|
||||
|
||||
func getJoinExec(namespace string, name string, dbPort int, replicaCount int) string {
|
||||
joinList := make([]string, 0)
|
||||
for i := 0; i < replicaCount; i++ {
|
||||
joinList = append(joinList, fmt.Sprintf("%s-%d.%s.%s:%d", name, i, name, namespace, dbPort))
|
||||
}
|
||||
joinListStr := strings.Join(joinList, ",")
|
||||
locality := "zone=" + namespace
|
||||
|
||||
return "exec /cockroach/cockroach start --logtostderr --certs-dir " + certPath + " --advertise-host $(hostname -f) --http-addr 0.0.0.0 --join " + joinListStr + " --locality " + locality + " --cache 25% --max-sql-memory 25%"
|
||||
}
|
||||
|
||||
func getResources(resourcesSFS *k8s.Resources) corev1.ResourceRequirements {
|
||||
internalResources := corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("100m"),
|
||||
"memory": resource.MustParse("512Mi"),
|
||||
},
|
||||
Limits: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("100m"),
|
||||
"memory": resource.MustParse("512Mi"),
|
||||
},
|
||||
}
|
||||
|
||||
if resourcesSFS != nil {
|
||||
internalResources = corev1.ResourceRequirements{}
|
||||
if resourcesSFS.Requests != nil {
|
||||
internalResources.Requests = resourcesSFS.Requests
|
||||
}
|
||||
if resourcesSFS.Limits != nil {
|
||||
internalResources.Limits = resourcesSFS.Limits
|
||||
}
|
||||
}
|
||||
|
||||
return internalResources
|
||||
}
|
||||
|
||||
func getAffinity(labels map[string]string) *corev1.Affinity {
|
||||
affinity := Affinitys{}
|
||||
for k, v := range labels {
|
||||
affinity = append(affinity, metav1.LabelSelectorRequirement{
|
||||
Key: k,
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{
|
||||
v,
|
||||
}})
|
||||
}
|
||||
sort.Sort(affinity)
|
||||
|
||||
return &corev1.Affinity{
|
||||
PodAntiAffinity: &corev1.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: affinity,
|
||||
},
|
||||
TopologyKey: "kubernetes.io/hostname",
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
package statefulset
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes/k8s"
|
||||
kubernetesmock "github.com/caos/orbos/pkg/kubernetes/mock"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/zitadel/operator/helpers"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStatefulset_JoinExec0(t *testing.T) {
|
||||
namespace := "testNs"
|
||||
name := "test"
|
||||
dbPort := 26257
|
||||
replicaCount := 0
|
||||
|
||||
equals := "exec /cockroach/cockroach start --logtostderr --certs-dir /cockroach/cockroach-certs --advertise-host $(hostname -f) --http-addr 0.0.0.0 --join --locality zone=testNs --cache 25% --max-sql-memory 25%"
|
||||
assert.Equal(t, equals, getJoinExec(namespace, name, dbPort, replicaCount))
|
||||
}
|
||||
|
||||
func TestStatefulset_JoinExec1(t *testing.T) {
|
||||
namespace := "testNs2"
|
||||
name := "test2"
|
||||
dbPort := 26257
|
||||
replicaCount := 1
|
||||
|
||||
equals := "exec /cockroach/cockroach start --logtostderr --certs-dir /cockroach/cockroach-certs --advertise-host $(hostname -f) --http-addr 0.0.0.0 --join test2-0.test2.testNs2:26257 --locality zone=testNs2 --cache 25% --max-sql-memory 25%"
|
||||
assert.Equal(t, equals, getJoinExec(namespace, name, dbPort, replicaCount))
|
||||
}
|
||||
|
||||
func TestStatefulset_JoinExec2(t *testing.T) {
|
||||
namespace := "testNs"
|
||||
name := "test"
|
||||
dbPort := 23
|
||||
replicaCount := 2
|
||||
|
||||
equals := "exec /cockroach/cockroach start --logtostderr --certs-dir /cockroach/cockroach-certs --advertise-host $(hostname -f) --http-addr 0.0.0.0 --join test-0.test.testNs:23,test-1.test.testNs:23 --locality zone=testNs --cache 25% --max-sql-memory 25%"
|
||||
assert.Equal(t, equals, getJoinExec(namespace, name, dbPort, replicaCount))
|
||||
}
|
||||
|
||||
func TestStatefulset_Resources0(t *testing.T) {
|
||||
equals := corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("100m"),
|
||||
"memory": resource.MustParse("512Mi"),
|
||||
},
|
||||
Limits: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("100m"),
|
||||
"memory": resource.MustParse("512Mi"),
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, equals, getResources(nil))
|
||||
}
|
||||
|
||||
func TestStatefulset_Resources1(t *testing.T) {
|
||||
res := &k8s.Resources{
|
||||
Requests: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("200m"),
|
||||
"memory": resource.MustParse("600Mi"),
|
||||
},
|
||||
Limits: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("500m"),
|
||||
"memory": resource.MustParse("126Mi"),
|
||||
},
|
||||
}
|
||||
|
||||
equals := corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("200m"),
|
||||
"memory": resource.MustParse("600Mi"),
|
||||
},
|
||||
Limits: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("500m"),
|
||||
"memory": resource.MustParse("126Mi"),
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, equals, getResources(res))
|
||||
}
|
||||
|
||||
func TestStatefulset_Resources2(t *testing.T) {
|
||||
res := &k8s.Resources{
|
||||
Requests: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("300m"),
|
||||
"memory": resource.MustParse("670Mi"),
|
||||
},
|
||||
Limits: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("600m"),
|
||||
"memory": resource.MustParse("256Mi"),
|
||||
},
|
||||
}
|
||||
|
||||
equals := corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("300m"),
|
||||
"memory": resource.MustParse("670Mi"),
|
||||
},
|
||||
Limits: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("600m"),
|
||||
"memory": resource.MustParse("256Mi"),
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, equals, getResources(res))
|
||||
}
|
||||
|
||||
func TestStatefulset_Adapt1(t *testing.T) {
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs"
|
||||
name := "test"
|
||||
image := "cockroach"
|
||||
nameLabels := labels.MustForName(labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd", "testOp", "testVersion"), "cockroachdb", "v0"), "testComponent"), name)
|
||||
k8sSelectableLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": name,
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"app.kubernetes.io/version": "testVersion",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "cockroachdb",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
k8sSelectorLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent",
|
||||
"app.kubernetes.io/managed-by": "testOp",
|
||||
"app.kubernetes.io/name": name,
|
||||
"app.kubernetes.io/part-of": "testProd",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
selector := labels.DeriveNameSelector(nameLabels, false)
|
||||
selectable := labels.AsSelectable(nameLabels)
|
||||
|
||||
serviceAccountName := "testSA"
|
||||
replicaCount := 1
|
||||
storageCapacity := "20Gi"
|
||||
dbPort := int32(26257)
|
||||
httpPort := int32(8080)
|
||||
storageClass := "testSC"
|
||||
nodeSelector := map[string]string{}
|
||||
tolerations := []corev1.Toleration{}
|
||||
resourcesSFS := &k8s.Resources{}
|
||||
|
||||
quantity, err := resource.ParseQuantity(storageCapacity)
|
||||
assert.NoError(t, err)
|
||||
|
||||
sfs := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sSelectableLabels,
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
ServiceName: name,
|
||||
Replicas: helpers.PointerInt32(int32(replicaCount)),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: k8sSelectorLabels,
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: k8sSelectableLabels,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
NodeSelector: nodeSelector,
|
||||
Tolerations: tolerations,
|
||||
ServiceAccountName: serviceAccountName,
|
||||
Affinity: getAffinity(k8sSelectableLabels),
|
||||
Containers: []corev1.Container{{
|
||||
Name: name,
|
||||
Image: image,
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
Ports: []corev1.ContainerPort{
|
||||
{ContainerPort: dbPort, Name: "grpc"},
|
||||
{ContainerPort: httpPort, Name: "http"},
|
||||
},
|
||||
LivenessProbe: &corev1.Probe{
|
||||
Handler: corev1.Handler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/health",
|
||||
Port: intstr.Parse("http"),
|
||||
Scheme: "HTTPS",
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 30,
|
||||
PeriodSeconds: 5,
|
||||
},
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
Handler: corev1.Handler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/health?ready=1",
|
||||
Port: intstr.Parse("http"),
|
||||
Scheme: "HTTPS",
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 10,
|
||||
PeriodSeconds: 5,
|
||||
FailureThreshold: 2,
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{{
|
||||
Name: datadirInternal,
|
||||
MountPath: datadirPath,
|
||||
}, {
|
||||
Name: certsInternal,
|
||||
MountPath: certPath,
|
||||
}, {
|
||||
Name: clientCertsInternal,
|
||||
MountPath: clientCertPath,
|
||||
}},
|
||||
Env: []corev1.EnvVar{{
|
||||
Name: "COCKROACH_CHANNEL",
|
||||
Value: "kubernetes-multiregion",
|
||||
}},
|
||||
Command: []string{
|
||||
"/bin/bash",
|
||||
"-ecx",
|
||||
getJoinExec(
|
||||
namespace,
|
||||
name,
|
||||
int(dbPort),
|
||||
replicaCount,
|
||||
),
|
||||
},
|
||||
Resources: getResources(resourcesSFS),
|
||||
}},
|
||||
Volumes: []corev1.Volume{{
|
||||
Name: datadirInternal,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: datadirInternal,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: certsInternal,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: nodeSecret,
|
||||
DefaultMode: helpers.PointerInt32(defaultMode),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: clientCertsInternal,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: rootSecret,
|
||||
DefaultMode: helpers.PointerInt32(defaultMode),
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
PodManagementPolicy: appsv1.PodManagementPolicyType("Parallel"),
|
||||
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
|
||||
Type: "RollingUpdate",
|
||||
},
|
||||
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: datadirInternal,
|
||||
},
|
||||
Spec: corev1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []corev1.PersistentVolumeAccessMode{
|
||||
corev1.PersistentVolumeAccessMode("ReadWriteOnce"),
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
"storage": quantity,
|
||||
},
|
||||
},
|
||||
StorageClassName: &storageClass,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ApplyStatefulSet(sfs, false)
|
||||
|
||||
query, _, _, _, _, err := AdaptFunc(
|
||||
monitor,
|
||||
selectable,
|
||||
selector,
|
||||
false,
|
||||
namespace,
|
||||
image,
|
||||
serviceAccountName,
|
||||
replicaCount,
|
||||
storageCapacity,
|
||||
dbPort,
|
||||
httpPort,
|
||||
storageClass,
|
||||
nodeSelector,
|
||||
tolerations,
|
||||
resourcesSFS,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
}
|
||||
|
||||
func TestStatefulset_Adapt2(t *testing.T) {
|
||||
k8sClient := kubernetesmock.NewMockClientInt(gomock.NewController(t))
|
||||
monitor := mntr.Monitor{}
|
||||
namespace := "testNs2"
|
||||
name := "test2"
|
||||
image := "cockroach2"
|
||||
|
||||
nameLabels := labels.MustForName(labels.MustForComponent(labels.MustForAPI(labels.MustForOperator("testProd2", "testOp2", "testVersion2"), "cockroachdb", "v0"), "testComponent2"), name)
|
||||
k8sSelectableLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent2",
|
||||
"app.kubernetes.io/managed-by": "testOp2",
|
||||
"app.kubernetes.io/name": name,
|
||||
"app.kubernetes.io/part-of": "testProd2",
|
||||
"app.kubernetes.io/version": "testVersion2",
|
||||
"caos.ch/apiversion": "v0",
|
||||
"caos.ch/kind": "cockroachdb",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
k8sSelectorLabels := map[string]string{
|
||||
"app.kubernetes.io/component": "testComponent2",
|
||||
"app.kubernetes.io/managed-by": "testOp2",
|
||||
"app.kubernetes.io/name": name,
|
||||
"app.kubernetes.io/part-of": "testProd2",
|
||||
"orbos.ch/selectable": "yes",
|
||||
}
|
||||
selector := labels.DeriveNameSelector(nameLabels, false)
|
||||
selectable := labels.AsSelectable(nameLabels)
|
||||
|
||||
serviceAccountName := "testSA2"
|
||||
replicaCount := 2
|
||||
storageCapacity := "40Gi"
|
||||
dbPort := int32(23)
|
||||
httpPort := int32(24)
|
||||
storageClass := "testSC2"
|
||||
nodeSelector := map[string]string{}
|
||||
tolerations := []corev1.Toleration{}
|
||||
resourcesSFS := &k8s.Resources{}
|
||||
|
||||
quantity, err := resource.ParseQuantity(storageCapacity)
|
||||
assert.NoError(t, err)
|
||||
|
||||
sfs := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Labels: k8sSelectableLabels,
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
ServiceName: name,
|
||||
Replicas: helpers.PointerInt32(int32(replicaCount)),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: k8sSelectorLabels,
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: k8sSelectableLabels,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
NodeSelector: nodeSelector,
|
||||
Tolerations: tolerations,
|
||||
ServiceAccountName: serviceAccountName,
|
||||
Affinity: getAffinity(k8sSelectableLabels),
|
||||
Containers: []corev1.Container{{
|
||||
Name: name,
|
||||
Image: image,
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
Ports: []corev1.ContainerPort{
|
||||
{ContainerPort: dbPort, Name: "grpc"},
|
||||
{ContainerPort: httpPort, Name: "http"},
|
||||
},
|
||||
LivenessProbe: &corev1.Probe{
|
||||
Handler: corev1.Handler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/health",
|
||||
Port: intstr.Parse("http"),
|
||||
Scheme: "HTTPS",
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 30,
|
||||
PeriodSeconds: 5,
|
||||
},
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
Handler: corev1.Handler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/health?ready=1",
|
||||
Port: intstr.Parse("http"),
|
||||
Scheme: "HTTPS",
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 10,
|
||||
PeriodSeconds: 5,
|
||||
FailureThreshold: 2,
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{{
|
||||
Name: datadirInternal,
|
||||
MountPath: datadirPath,
|
||||
}, {
|
||||
Name: certsInternal,
|
||||
MountPath: certPath,
|
||||
}, {
|
||||
Name: clientCertsInternal,
|
||||
MountPath: clientCertPath,
|
||||
}},
|
||||
Env: []corev1.EnvVar{{
|
||||
Name: "COCKROACH_CHANNEL",
|
||||
Value: "kubernetes-multiregion",
|
||||
}},
|
||||
Command: []string{
|
||||
"/bin/bash",
|
||||
"-ecx",
|
||||
getJoinExec(
|
||||
namespace,
|
||||
name,
|
||||
int(dbPort),
|
||||
replicaCount,
|
||||
),
|
||||
},
|
||||
Resources: getResources(resourcesSFS),
|
||||
}},
|
||||
Volumes: []corev1.Volume{{
|
||||
Name: datadirInternal,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: datadirInternal,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: certsInternal,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: nodeSecret,
|
||||
DefaultMode: helpers.PointerInt32(defaultMode),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: clientCertsInternal,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: rootSecret,
|
||||
DefaultMode: helpers.PointerInt32(defaultMode),
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
PodManagementPolicy: appsv1.PodManagementPolicyType("Parallel"),
|
||||
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
|
||||
Type: "RollingUpdate",
|
||||
},
|
||||
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: datadirInternal,
|
||||
},
|
||||
Spec: corev1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []corev1.PersistentVolumeAccessMode{
|
||||
corev1.PersistentVolumeAccessMode("ReadWriteOnce"),
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
"storage": quantity,
|
||||
},
|
||||
},
|
||||
StorageClassName: &storageClass,
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient.EXPECT().ApplyStatefulSet(sfs, false)
|
||||
|
||||
query, _, _, _, _, err := AdaptFunc(
|
||||
monitor,
|
||||
selectable,
|
||||
selector,
|
||||
false,
|
||||
namespace,
|
||||
image,
|
||||
serviceAccountName,
|
||||
replicaCount,
|
||||
storageCapacity,
|
||||
dbPort,
|
||||
httpPort,
|
||||
storageClass,
|
||||
nodeSelector,
|
||||
tolerations,
|
||||
resourcesSFS,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ensure, err := query(k8sClient)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ensure)
|
||||
|
||||
assert.NoError(t, ensure(k8sClient))
|
||||
|
||||
}
|
||||
72
operator/database/kinds/databases/managed/user/adapt.go
Normal file
72
operator/database/kinds/databases/managed/user/adapt.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/caos/zitadel/operator"
|
||||
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/labels"
|
||||
"github.com/caos/zitadel/operator/database/kinds/databases/managed/certificate"
|
||||
)
|
||||
|
||||
func AdaptFunc(
|
||||
monitor mntr.Monitor,
|
||||
namespace string,
|
||||
deployName string,
|
||||
containerName string,
|
||||
certsDir string,
|
||||
userName string,
|
||||
password string,
|
||||
componentLabels *labels.Component,
|
||||
) (
|
||||
operator.QueryFunc,
|
||||
operator.DestroyFunc,
|
||||
error,
|
||||
) {
|
||||
cmdSql := fmt.Sprintf("cockroach sql --certs-dir=%s", certsDir)
|
||||
|
||||
createSql := fmt.Sprintf("CREATE USER IF NOT EXISTS %s ", userName)
|
||||
if password != "" {
|
||||
createSql = fmt.Sprintf("%s WITH PASSWORD %s", createSql, password)
|
||||
}
|
||||
|
||||
deleteSql := fmt.Sprintf("DROP USER IF EXISTS %s", userName)
|
||||
|
||||
_, _, addUserFunc, deleteUserFunc, _, err := certificate.AdaptFunc(monitor, namespace, componentLabels, "", false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
addUser, err := addUserFunc(userName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ensureUser := func(k8sClient kubernetes.ClientInt) error {
|
||||
return k8sClient.ExecInPodOfDeployment(namespace, deployName, containerName, fmt.Sprintf("%s -e '%s;'", cmdSql, createSql))
|
||||
}
|
||||
|
||||
deleteUser, err := deleteUserFunc(userName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
destoryUser := func(k8sClient kubernetes.ClientInt) error {
|
||||
return k8sClient.ExecInPodOfDeployment(namespace, deployName, containerName, fmt.Sprintf("%s -e '%s;'", cmdSql, deleteSql))
|
||||
}
|
||||
|
||||
queriers := []operator.QueryFunc{
|
||||
addUser,
|
||||
operator.EnsureFuncToQueryFunc(ensureUser),
|
||||
}
|
||||
|
||||
destroyers := []operator.DestroyFunc{
|
||||
destoryUser,
|
||||
deleteUser,
|
||||
}
|
||||
|
||||
return func(k8sClient kubernetes.ClientInt, queried map[string]interface{}) (operator.EnsureFunc, error) {
|
||||
return operator.QueriersToEnsureFunc(monitor, false, queriers, k8sClient, queried)
|
||||
},
|
||||
operator.DestroyersToDestroyFunc(monitor, destroyers),
|
||||
nil
|
||||
}
|
||||
59
operator/database/kinds/databases/provided/adapt.go
Normal file
59
operator/database/kinds/databases/provided/adapt.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package provided
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/mntr"
|
||||
"github.com/caos/orbos/pkg/kubernetes"
|
||||
"github.com/caos/orbos/pkg/secret"
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/caos/zitadel/operator"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func AdaptFunc() func(
|
||||
monitor mntr.Monitor,
|
||||
desired *tree.Tree,
|
||||
current *tree.Tree,
|
||||
) (
|
||||
operator.QueryFunc,
|
||||
operator.DestroyFunc,
|
||||
map[string]*secret.Secret,
|
||||
error,
|
||||
) {
|
||||
return func(
|
||||
monitor mntr.Monitor,
|
||||
desired *tree.Tree,
|
||||
current *tree.Tree,
|
||||
) (
|
||||
operator.QueryFunc,
|
||||
operator.DestroyFunc,
|
||||
map[string]*secret.Secret,
|
||||
error,
|
||||
) {
|
||||
desiredKind, err := parseDesiredV0(desired)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(err, "parsing desired state failed")
|
||||
}
|
||||
desired.Parsed = desiredKind
|
||||
|
||||
currentDB := &Current{
|
||||
Common: &tree.Common{
|
||||
Kind: "databases.caos.ch/ProvidedDatabase",
|
||||
Version: "v0",
|
||||
},
|
||||
}
|
||||
current.Parsed = currentDB
|
||||
|
||||
return func(k8sClient kubernetes.ClientInt, _ map[string]interface{}) (operator.EnsureFunc, error) {
|
||||
currentDB.Current.URL = desiredKind.Spec.URL
|
||||
currentDB.Current.Port = desiredKind.Spec.Port
|
||||
|
||||
return func(k8sClient kubernetes.ClientInt) error {
|
||||
return nil
|
||||
}, nil
|
||||
}, func(k8sClient kubernetes.ClientInt) error {
|
||||
return nil
|
||||
},
|
||||
map[string]*secret.Secret{},
|
||||
nil
|
||||
}
|
||||
}
|
||||
21
operator/database/kinds/databases/provided/current.go
Normal file
21
operator/database/kinds/databases/provided/current.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package provided
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
)
|
||||
|
||||
type Current struct {
|
||||
Common *tree.Common `yaml:",inline"`
|
||||
Current struct {
|
||||
URL string
|
||||
Port string
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Current) GetURL() string {
|
||||
return c.Current.URL
|
||||
}
|
||||
|
||||
func (c *Current) GetPort() string {
|
||||
return c.Current.Port
|
||||
}
|
||||
32
operator/database/kinds/databases/provided/desired.go
Normal file
32
operator/database/kinds/databases/provided/desired.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package provided
|
||||
|
||||
import (
|
||||
"github.com/caos/orbos/pkg/tree"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type DesiredV0 struct {
|
||||
Common *tree.Common `yaml:",inline"`
|
||||
Spec Spec
|
||||
}
|
||||
|
||||
type Spec struct {
|
||||
Verbose bool
|
||||
Namespace string
|
||||
URL string
|
||||
Port string
|
||||
Users []string
|
||||
}
|
||||
|
||||
func parseDesiredV0(desiredTree *tree.Tree) (*DesiredV0, error) {
|
||||
desiredKind := &DesiredV0{
|
||||
Common: desiredTree.Common,
|
||||
Spec: Spec{},
|
||||
}
|
||||
|
||||
if err := desiredTree.Original.Decode(desiredKind); err != nil {
|
||||
return nil, errors.Wrap(err, "parsing desired state failed")
|
||||
}
|
||||
|
||||
return desiredKind, nil
|
||||
}
|
||||
Reference in New Issue
Block a user