From 514aad04cd8da18ea32b031133b69918d5bf6051 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Dec 2022 17:57:28 +0000 Subject: [PATCH 001/153] build(deps): bump qs from 6.5.2 to 6.5.3 in /docs Bumps [qs](https://github.com/ljharb/qs) from 6.5.2 to 6.5.3. - [Release notes](https://github.com/ljharb/qs/releases) - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.5.2...v6.5.3) --- updated-dependencies: - dependency-name: qs dependency-type: indirect ... Signed-off-by: dependabot[bot] --- docs/package-lock.json | 18 +++++++++--------- docs/yarn.lock | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 7c15871f..40469fa4 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -11476,9 +11476,9 @@ }, "dependencies": { "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" } } }, @@ -12846,9 +12846,9 @@ } }, "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -12928,9 +12928,9 @@ } }, "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", diff --git a/docs/yarn.lock b/docs/yarn.lock index 3fec7974..117a9d5b 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -8117,9 +8117,9 @@ qs@6.7.0: integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== query-string@^5.0.1: version "5.1.1" From 4988655efca3ea9c3b7f4ac9b3c27de7da0edac8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Dec 2022 18:49:59 +0000 Subject: [PATCH 002/153] build(deps): bump express from 4.17.1 to 4.18.2 in /docs Bumps [express](https://github.com/expressjs/express) from 4.17.1 to 4.18.2. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.17.1...4.18.2) --- updated-dependencies: - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] --- docs/package-lock.json | 396 +++++++++++++++++++++-------------------- docs/yarn.lock | 311 ++++++++++++++++++-------------- 2 files changed, 386 insertions(+), 321 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 40469fa4..0792dfc2 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -2810,38 +2810,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -3734,11 +3702,6 @@ } } }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -4501,11 +4464,6 @@ "minimalistic-assert": "^1.0.0" } }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, "detab": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.4.tgz", @@ -5124,42 +5082,89 @@ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "dependencies": { + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5168,15 +5173,154 @@ "ms": "2.0.0" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" } } }, @@ -5447,35 +5591,6 @@ "to-regex-range": "^5.0.1" } }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, "find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", @@ -6619,25 +6734,6 @@ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, "http-parser-js": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", @@ -8616,14 +8712,6 @@ "es-abstract": "^1.19.1" } }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -10859,11 +10947,6 @@ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, "query-string": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", @@ -10922,17 +11005,6 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -11858,48 +11930,6 @@ } } }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, "serialize-javascript": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", @@ -11908,17 +11938,6 @@ "randombytes": "^2.1.0" } }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -11950,11 +11969,6 @@ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", diff --git a/docs/yarn.lock b/docs/yarn.lock index 117a9d5b..2ca1d5eb 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -1455,13 +1455,13 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -accepts@^1.3.7, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== +accepts@^1.3.7, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" + mime-types "~2.1.34" + negotiator "0.6.3" acorn-node@^1.6.1: version "1.8.2" @@ -1654,7 +1654,7 @@ array-find-index@^1.0.1: array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== array-union@^1.0.1, array-union@^1.0.2: version "1.0.2" @@ -1953,21 +1953,23 @@ bn.js@^5.0.0, bn.js@^5.1.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== dependencies: - bytes "3.1.0" + bytes "3.1.2" content-type "~1.0.4" debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" @@ -2156,6 +2158,11 @@ bytes@3.1.0, bytes@^3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + cacache@^12.0.2: version "12.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" @@ -2698,12 +2705,12 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -content-disposition@0.5.3, content-disposition@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== +content-disposition@0.5.4, content-disposition@^0.5.2: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: - safe-buffer "5.1.2" + safe-buffer "5.2.1" content-type@^1.0.4, content-type@~1.0.4: version "1.0.4" @@ -2720,12 +2727,12 @@ convert-source-map@^1.7.0: cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== copy-concurrently@^1.0.0: version "1.0.5" @@ -3284,6 +3291,11 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -3297,10 +3309,10 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== detab@^2.0.0: version "2.0.4" @@ -3517,7 +3529,7 @@ ecc-jsbn@~0.1.1: ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.3.867: version "1.3.867" @@ -3555,7 +3567,7 @@ emojis-list@^3.0.0: encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" @@ -3697,7 +3709,7 @@ esutils@^2.0.2: etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== eventemitter3@^3.1.0: version "3.1.2" @@ -3810,37 +3822,38 @@ express-graphql@^0.9.0: raw-body "^2.4.1" express@^4.16.4: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== dependencies: - accepts "~1.3.7" + accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" + body-parser "1.20.1" + content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.0" + cookie "0.5.0" cookie-signature "1.0.6" debug "2.6.9" - depd "~1.1.2" + depd "2.0.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "~1.1.2" + finalhandler "1.2.0" fresh "0.5.2" + http-errors "2.0.0" merge-descriptors "1.0.1" methods "~1.1.2" - on-finished "~2.3.0" + on-finished "2.4.1" parseurl "~1.3.3" path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" + proxy-addr "~2.0.7" + qs "6.11.0" range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" @@ -4052,17 +4065,17 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== dependencies: debug "2.6.9" encodeurl "~1.0.2" escape-html "~1.0.3" - on-finished "~2.3.0" + on-finished "2.4.1" parseurl "~1.3.3" - statuses "~1.5.0" + statuses "2.0.1" unpipe "~1.0.0" find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: @@ -4157,7 +4170,7 @@ fragment-cache@^0.2.1: fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== friendly-errors-webpack-plugin@^1.7.0: version "1.7.0" @@ -4272,7 +4285,16 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: +get-intrinsic@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== @@ -4692,6 +4714,11 @@ has-symbols@^1.0.1, has-symbols@^1.0.2: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + has-to-string-tag-x@^1.2.0: version "1.4.1" resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" @@ -4955,18 +4982,7 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@1.7.3, http-errors@~1.7.2: +http-errors@1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== @@ -4977,6 +4993,17 @@ http-errors@1.7.3, http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + http-errors@^1.7.3: version "1.8.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507" @@ -6203,7 +6230,7 @@ mdurl@^1.0.1: media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== memory-fs@^0.4.1: version "0.4.1" @@ -6240,7 +6267,7 @@ meow@^3.3.0: merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== merge-source-map@^1.1.0: version "1.1.0" @@ -6264,7 +6291,7 @@ merge2@^1.2.3, merge2@^1.3.0: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" @@ -6306,13 +6333,25 @@ mime-db@1.50.0, mime-db@^1.28.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f" integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A== -mime-types@^2.1.12, mime-types@^2.1.21, mime-types@~2.1.19, mime-types@~2.1.24: +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.21, mime-types@~2.1.19: version "2.1.33" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb" integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g== dependencies: mime-db "1.50.0" +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -6469,19 +6508,14 @@ mozjpeg@^6.0.0: ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -6518,10 +6552,10 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2: version "2.6.2" @@ -6722,11 +6756,16 @@ object-hash@^2.2.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== -object-inspect@^1.11.0, object-inspect@^1.9.0: +object-inspect@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== +object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -6779,10 +6818,10 @@ object.values@^1.1.0: define-properties "^1.1.3" es-abstract "^1.19.1" -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== dependencies: ee-first "1.1.1" @@ -7091,7 +7130,7 @@ path-parse@^1.0.6: path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== path-to-regexp@^2.2.1: version "2.4.0" @@ -8014,7 +8053,7 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -proxy-addr@~2.0.5: +proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== @@ -8111,10 +8150,12 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" qs@~6.5.2: version "6.5.3" @@ -8180,13 +8221,13 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== dependencies: - bytes "3.1.0" - http-errors "1.7.2" + bytes "3.1.2" + http-errors "2.0.0" iconv-lite "0.4.24" unpipe "1.0.0" @@ -8615,16 +8656,16 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -8744,24 +8785,24 @@ semver@^7.3.2: dependencies: lru-cache "^6.0.0" -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== dependencies: debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" + depd "2.0.0" + destroy "1.2.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" - http-errors "~1.7.2" + http-errors "2.0.0" mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" + ms "2.1.3" + on-finished "2.4.1" range-parser "~1.2.1" - statuses "~1.5.0" + statuses "2.0.1" serialize-javascript@^3.1.0: version "3.1.0" @@ -8777,15 +8818,15 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.17.1" + send "0.18.0" set-blocking@~2.0.0: version "2.0.0" @@ -9151,7 +9192,12 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -9640,6 +9686,11 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + toposort@^1.0.0: version "1.0.7" resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" @@ -9707,7 +9758,7 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: +type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -9953,7 +10004,7 @@ universalify@^2.0.0: unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== unquote@~1.1.1: version "1.1.1" @@ -10114,7 +10165,7 @@ utila@~0.4: utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" @@ -10132,7 +10183,7 @@ validate-npm-package-license@^3.0.1: vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== vendors@^1.0.0: version "1.0.4" From 3efb91f264c15fe351370fe3468a25cc3c4ef182 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Dec 2022 23:26:14 +0000 Subject: [PATCH 003/153] build(deps): bump decode-uri-component from 0.2.0 to 0.2.2 in /docs Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2. - [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases) - [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2) --- updated-dependencies: - dependency-name: decode-uri-component dependency-type: indirect ... Signed-off-by: dependabot[bot] --- docs/package-lock.json | 6 +++--- docs/yarn.lock | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 0792dfc2..b99a761d 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -4232,9 +4232,9 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" }, "decompress": { "version": "4.2.1", diff --git a/docs/yarn.lock b/docs/yarn.lock index 2ca1d5eb..cdf3b549 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -3142,9 +3142,9 @@ decamelize@^1.1.2: integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== decompress-response@^3.2.0, decompress-response@^3.3.0: version "3.3.0" From ee213aab491754db3f4026e3f8c87873266f0529 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 17 Dec 2022 15:51:56 +0100 Subject: [PATCH 004/153] chore(gh): updating compatibility matrix --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ed9944eb..76a5db96 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - k8s-version: ['v1.16.15', 'v1.17.11', 'v1.18.8', 'v1.19.4', 'v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.1'] + k8s-version: ['v1.16.15', 'v1.17.11', 'v1.18.8', 'v1.19.4', 'v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.0'] runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 From fb5c1a1fa6e31ff2d23070fc5950c60138c088f4 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 17 Dec 2022 15:54:34 +0100 Subject: [PATCH 005/153] feat: supporting k8s >= 1.25 --- controllers/servicelabels/endpoint_slices.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/controllers/servicelabels/endpoint_slices.go b/controllers/servicelabels/endpoint_slices.go index c73ff6cf..81f098e9 100644 --- a/controllers/servicelabels/endpoint_slices.go +++ b/controllers/servicelabels/endpoint_slices.go @@ -26,14 +26,14 @@ func (r *EndpointSlicesLabelsReconciler) SetupWithManager(ctx context.Context, m } switch { - case r.VersionMajor == 1 && r.VersionMinor <= 16: + case r.VersionMajor == 1 && r.VersionMinor < 16: r.Log.Info("Skipping controller setup, as EndpointSlices are not supported on current kubernetes version", "VersionMajor", r.VersionMajor, "VersionMinor", r.VersionMinor) return nil - case r.VersionMajor == 1 && r.VersionMinor >= 21: - r.abstractServiceLabelsReconciler.obj = &discoveryv1.EndpointSlice{} - default: + case r.VersionMajor == 1 && r.VersionMinor < 25: r.abstractServiceLabelsReconciler.obj = &discoveryv1beta1.EndpointSlice{} + default: + r.abstractServiceLabelsReconciler.obj = &discoveryv1.EndpointSlice{} } return ctrl.NewControllerManagedBy(mgr). From d2a63585174a2c682c4ae3587275b020756354a5 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 17 Dec 2022 15:57:16 +0100 Subject: [PATCH 006/153] test: supporting k8s >= 1.25 --- e2e/service_metadata_test.go | 62 +++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index 22f02df8..405b93b8 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -10,10 +10,10 @@ import ( "errors" "fmt" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" discoveryv1beta1 "k8s.io/api/discovery/v1beta1" networkingv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/meta" @@ -21,6 +21,9 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" ) var _ = Describe("adding metadata to Service objects", func() { @@ -228,23 +231,46 @@ var _ = Describe("adding metadata to Service objects", func() { NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) - eps := &discoveryv1beta1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{ - Name: "endpointslice-metadata", - Namespace: ns.GetName(), - }, - AddressType: discoveryv1beta1.AddressTypeIPv4, - Endpoints: []discoveryv1beta1.Endpoint{ - { - Addresses: []string{"10.10.1.1"}, + var eps client.Object + + if version := GetKubernetesVersion(); version.Major() == 1 && version.Minor() < 25 { + eps = &discoveryv1beta1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "endpointslice-metadata", + Namespace: ns.GetName(), }, - }, - Ports: []discoveryv1beta1.EndpointPort{ - { - Name: pointer.StringPtr("foo"), - Port: pointer.Int32Ptr(9999), + AddressType: discoveryv1beta1.AddressTypeIPv4, + Endpoints: []discoveryv1beta1.Endpoint{ + { + Addresses: []string{"10.10.1.1"}, + }, }, - }, + Ports: []discoveryv1beta1.EndpointPort{ + { + Name: pointer.StringPtr("foo"), + Port: pointer.Int32Ptr(9999), + }, + }, + } + } else { + eps = &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "endpointslice-metadata", + Namespace: ns.GetName(), + }, + AddressType: discoveryv1.AddressTypeIPv4, + Endpoints: []discoveryv1.Endpoint{ + { + Addresses: []string{"10.10.1.1"}, + }, + }, + Ports: []discoveryv1.EndpointPort{ + { + Name: pointer.StringPtr("foo"), + Port: pointer.Int32Ptr(9999), + }, + }, + } } // Waiting for the reconciliation of required RBAC EventuallyCreation(func() (err error) { @@ -274,7 +300,7 @@ var _ = Describe("adding metadata to Service objects", func() { Eventually(func() (ok bool) { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed()) for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.Annotations { - ok, _ = HaveKeyWithValue(k, v).Match(eps.Annotations) + ok, _ = HaveKeyWithValue(k, v).Match(eps.GetAnnotations()) if !ok { return false } @@ -287,7 +313,7 @@ var _ = Describe("adding metadata to Service objects", func() { Eventually(func() (ok bool) { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed()) for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.Labels { - ok, _ = HaveKeyWithValue(k, v).Match(eps.Labels) + ok, _ = HaveKeyWithValue(k, v).Match(eps.GetLabels()) if !ok { return false } From 1087ea853b7b41492c56fb71f00a8038cf71fede Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 24 Nov 2022 18:31:14 +0100 Subject: [PATCH 007/153] fix: inverted logic in forbidden user namespace metadata --- pkg/webhook/namespace/user_metadata.go | 32 +++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/pkg/webhook/namespace/user_metadata.go b/pkg/webhook/namespace/user_metadata.go index ac34be46..b4dc3489 100644 --- a/pkg/webhook/namespace/user_metadata.go +++ b/pkg/webhook/namespace/user_metadata.go @@ -131,26 +131,36 @@ func (r *userMetadataHandler) OnUpdate(client client.Client, decoder *admission. } } - var labels, annotations map[string]string + labels, annotations := oldNs.GetLabels(), oldNs.GetAnnotations() for key, value := range newNs.GetLabels() { - if _, ok := oldNs.GetLabels()[key]; ok { - if labels == nil { - labels = make(map[string]string) - } - + v, ok := labels[key] + if !ok { labels[key] = value + + continue } + + if v != value { + continue + } + + delete(labels, key) } for key, value := range newNs.GetAnnotations() { - if _, ok := oldNs.GetAnnotations()[key]; ok { - if annotations == nil { - annotations = make(map[string]string) - } - + v, ok := annotations[key] + if !ok { annotations[key] = value + + continue } + + if v != value { + continue + } + + delete(annotations, key) } return r.validateUserMetadata(tnt, recorder, labels, annotations) From 1d431346a3c3cb881cb75b4562886c4bc6767f47 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 24 Nov 2022 19:02:36 +0100 Subject: [PATCH 008/153] test: update of namespace forbidden metadata should not succeed --- e2e/namespace_user_metadata_test.go | 122 +++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) diff --git a/e2e/namespace_user_metadata_test.go b/e2e/namespace_user_metadata_test.go index 2abcea68..55243f79 100644 --- a/e2e/namespace_user_metadata_test.go +++ b/e2e/namespace_user_metadata_test.go @@ -7,11 +7,15 @@ package e2e import ( "context" + "time" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" ) var _ = Describe("creating a Namespace with user-specified labels and annotations", func() { @@ -58,7 +62,7 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation }) }) - It("should fail", func() { + It("should fail when creating a Namespace", func() { By("specifying forbidden labels using exact match", func() { ns := NewNamespace("namespace-user-metadata-forbidden-labels") ns.SetLabels(map[string]string{"foo": "bar"}) @@ -80,4 +84,118 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) }) + + It("should fail when updating a Namespace", func() { + role := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns-patch", + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"patch", "update"}, + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + }, + }, + } + + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns-patch", + }, + Subjects: []rbacv1.Subject{ + { + APIGroup: "rbac.authorization.k8s.io", + Kind: tnt.Spec.Owners[0].Kind.String(), + Name: tnt.Spec.Owners[0].Name, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: role.GetName(), + }, + } + + rbacPatch := func(ns string) { + role := role.DeepCopy() + role.SetNamespace(ns) + Expect(k8sClient.Create(context.Background(), role)).To(Succeed()) + + roleBinding := roleBinding.DeepCopy() + roleBinding.SetNamespace(ns) + Expect(k8sClient.Create(context.Background(), roleBinding)).To(Succeed()) + } + + cs := ownerClient(tnt.Spec.Owners[0]) + + By("specifying forbidden labels using exact match", func() { + ns := NewNamespace("forbidden-labels-exact-match") + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + rbacPatch(ns.GetName()) + Consistently(func() error { + if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil { + return nil + } + + ns.SetLabels(map[string]string{"foo": "bar"}) + + _, err := cs.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{}) + + return err + }, 10*time.Second, time.Second).ShouldNot(Succeed()) + }) + By("specifying forbidden labels using regex match", func() { + ns := NewNamespace("forbidden-labels-regex-match") + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + rbacPatch(ns.GetName()) + Consistently(func() error { + if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil { + return nil + } + + ns.SetLabels(map[string]string{"gatsby-foo": "bar"}) + + _, err := cs.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{}) + + return err + }, 3*time.Second, time.Second).ShouldNot(Succeed()) + }) + By("specifying forbidden annotations using exact match", func() { + ns := NewNamespace("forbidden-annotations-exact-match") + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + rbacPatch(ns.GetName()) + Consistently(func() error { + if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil { + return nil + } + + ns.SetAnnotations(map[string]string{"foo": "bar"}) + + _, err := cs.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{}) + + return err + }, 10*time.Second, time.Second).ShouldNot(Succeed()) + }) + By("specifying forbidden annotations using regex match", func() { + ns := NewNamespace("forbidden-annotations-regex-match") + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + rbacPatch(ns.GetName()) + Consistently(func() error { + if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil { + return nil + } + + ns.SetAnnotations(map[string]string{"gatsby-foo": "bar"}) + + _, err := cs.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{}) + + return err + }, 10*time.Second, time.Second).ShouldNot(Succeed()) + }) + }) }) From 793d847a0b7dbd105b6b3a033dc59014209f29f7 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Wed, 21 Sep 2022 10:20:37 +0200 Subject: [PATCH 009/153] perf(dockerfile): using go modules caching --- Dockerfile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 097361bc..7dcab99c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,6 @@ # Build the manager binary FROM golang:1.18 as builder -ARG TARGETARCH -ARG GIT_HEAD_COMMIT -ARG GIT_TAG_COMMIT -ARG GIT_LAST_TAG -ARG GIT_MODIFIED -ARG GIT_REPO -ARG BUILD_DATE - WORKDIR /workspace # Copy the Go Modules manifests COPY go.mod go.mod @@ -17,6 +9,14 @@ COPY go.sum go.sum # and so that source changes don't invalidate our downloaded layer RUN go mod download +ARG TARGETARCH +ARG GIT_HEAD_COMMIT +ARG GIT_TAG_COMMIT +ARG GIT_LAST_TAG +ARG GIT_MODIFIED +ARG GIT_REPO +ARG BUILD_DATE + # Copy the go source COPY main.go main.go COPY version.go version.go From 4100b98fad243e1e6dacd174c9aab98f5c0119b3 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 22 Sep 2022 22:29:37 +0200 Subject: [PATCH 010/153] chore(makefile): downloading locally golangci-lint --- .golangci.yml | 2 +- Makefile | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 9980ae83..7f62eb65 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -40,7 +40,7 @@ issues: - Using the variable on range scope .* in function literal service: - golangci-lint-version: 1.33.x + golangci-lint-version: 1.45.2 run: skip-files: diff --git a/Makefile b/Makefile index c4b6b668..ab42d297 100644 --- a/Makefile +++ b/Makefile @@ -213,10 +213,14 @@ bundle-build: goimports: goimports -w -l -local "github.com/clastix/capsule" . +GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint +golangci-lint: ## Download golangci-lint locally if necessary. + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.2) + # Linting code as PR is expecting .PHONY: golint -golint: - golangci-lint run -c .golangci.yml +golint: golangci-lint + $(GOLANGCI_LINT) run -c .golangci.yml # Running e2e tests in a KinD instance .PHONY: e2e From 9f9ccf0bb195472d23637740bfb8a26702e91564 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 28 Oct 2022 18:20:05 -0400 Subject: [PATCH 011/153] chore(controller-gen): upgrading to 0.10.0 --- Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index ab42d297..451cfb3a 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,6 @@ BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) # Image URL to use all building/pushing image targets IMG ?= clastix/capsule:$(VERSION) -# Produce CRDs that work back to Kubernetes 1.11 (no version conversion) -CRD_OPTIONS ?= "crd:preserveUnknownFields=false" # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -72,7 +70,7 @@ remove: installer # Generate manifests e.g. CRD, RBAC etc. manifests: controller-gen - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases # Generate code generate: controller-gen @@ -164,7 +162,7 @@ docker-push: CONTROLLER_GEN = $(shell pwd)/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. - $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0) + $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.10.0) APIDOCS_GEN = $(shell pwd)/bin/crdoc apidocs-gen: ## Download crdoc locally if necessary. From bca70e634d23c84c802ff82cdd6218b680686b9a Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 20 Sep 2022 18:35:25 +0200 Subject: [PATCH 012/153] feat: introducing v1beta2 api group --- PROJECT | 17 + api/v1alpha1/capsuleconfiguration_types.go | 3 + api/v1beta1/allowed_list.go | 1 + api/v1beta1/deny_wildcard.go | 4 +- api/v1beta1/forbidden_list.go | 1 + api/v1beta2/additional_metadata.go | 9 + api/v1beta2/additional_role_bindings.go | 12 + api/v1beta2/allowed_list.go | 37 ++ api/v1beta2/allowed_list_test.go | 73 +++ api/v1beta2/capsuleconfiguration_funcs.go | 131 ++++ api/v1beta2/capsuleconfiguration_types.go | 75 +++ api/v1beta2/conversion_hub.go | 401 ++++++++++++ api/v1beta2/custom_resource_quota.go | 59 ++ api/v1beta2/forbidden_list.go | 37 ++ api/v1beta2/forbidden_list_test.go | 73 +++ api/v1beta2/groupversion_info.go | 23 + api/v1beta2/hostname_collision_scope.go | 14 + api/v1beta2/image_pull_policy.go | 11 + api/v1beta2/ingress_options.go | 26 + api/v1beta2/limit_ranges.go | 10 + api/v1beta2/namespace_options.go | 16 + api/v1beta2/network_policy.go | 12 + api/v1beta2/owner.go | 57 ++ api/v1beta2/owner_list.go | 41 ++ api/v1beta2/owner_list_test.go | 86 +++ api/v1beta2/resource_quota.go | 21 + api/v1beta2/service_allowed_ips.go | 11 + api/v1beta2/service_allowed_types.go | 16 + api/v1beta2/service_options.go | 13 + api/v1beta2/tenant_annotations.go | 26 + api/v1beta2/tenant_func.go | 38 ++ api/v1beta2/tenant_labels.go | 32 + api/v1beta2/tenant_status.go | 23 + api/v1beta2/tenant_types.go | 74 +++ api/v1beta2/zz_generated.deepcopy.go | 672 +++++++++++++++++++++ main.go | 2 + 36 files changed, 2155 insertions(+), 2 deletions(-) create mode 100644 api/v1beta2/additional_metadata.go create mode 100644 api/v1beta2/additional_role_bindings.go create mode 100644 api/v1beta2/allowed_list.go create mode 100644 api/v1beta2/allowed_list_test.go create mode 100644 api/v1beta2/capsuleconfiguration_funcs.go create mode 100644 api/v1beta2/capsuleconfiguration_types.go create mode 100644 api/v1beta2/conversion_hub.go create mode 100644 api/v1beta2/custom_resource_quota.go create mode 100644 api/v1beta2/forbidden_list.go create mode 100644 api/v1beta2/forbidden_list_test.go create mode 100644 api/v1beta2/groupversion_info.go create mode 100644 api/v1beta2/hostname_collision_scope.go create mode 100644 api/v1beta2/image_pull_policy.go create mode 100644 api/v1beta2/ingress_options.go create mode 100644 api/v1beta2/limit_ranges.go create mode 100644 api/v1beta2/namespace_options.go create mode 100644 api/v1beta2/network_policy.go create mode 100644 api/v1beta2/owner.go create mode 100644 api/v1beta2/owner_list.go create mode 100644 api/v1beta2/owner_list_test.go create mode 100644 api/v1beta2/resource_quota.go create mode 100644 api/v1beta2/service_allowed_ips.go create mode 100644 api/v1beta2/service_allowed_types.go create mode 100644 api/v1beta2/service_options.go create mode 100644 api/v1beta2/tenant_annotations.go create mode 100644 api/v1beta2/tenant_func.go create mode 100644 api/v1beta2/tenant_labels.go create mode 100644 api/v1beta2/tenant_status.go create mode 100644 api/v1beta2/tenant_types.go create mode 100644 api/v1beta2/zz_generated.deepcopy.go diff --git a/PROJECT b/PROJECT index 78f90d36..72aada3c 100644 --- a/PROJECT +++ b/PROJECT @@ -36,4 +36,21 @@ resources: kind: Tenant path: github.com/clastix/capsule/api/v1beta1 version: v1beta1 +- api: + crdVersion: v1 + namespaced: false + domain: clastix.io + group: capsule + kind: Tenant + path: github.com/clastix/capsule/api/v1beta2 + version: v1beta2 +- api: + crdVersion: v1 + namespaced: false + controller: true + domain: clastix.io + group: capsule + kind: CapsuleConfiguration + path: github.com/clastix/capsule/api/v1beta2 + version: v1beta2 version: "3" diff --git a/api/v1alpha1/capsuleconfiguration_types.go b/api/v1alpha1/capsuleconfiguration_types.go index 21b6e9df..147bc485 100644 --- a/api/v1alpha1/capsuleconfiguration_types.go +++ b/api/v1alpha1/capsuleconfiguration_types.go @@ -20,6 +20,7 @@ type CapsuleConfigurationSpec struct { ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"` } +// +kubebuilder:storageversion // +kubebuilder:object:root=true // +kubebuilder:resource:scope=Cluster @@ -31,6 +32,8 @@ type CapsuleConfiguration struct { Spec CapsuleConfigurationSpec `json:"spec,omitempty"` } +func (in *CapsuleConfiguration) Hub() {} + // +kubebuilder:object:root=true // CapsuleConfigurationList contains a list of CapsuleConfiguration. diff --git a/api/v1beta1/allowed_list.go b/api/v1beta1/allowed_list.go index b5fd1aff..d7e24cac 100644 --- a/api/v1beta1/allowed_list.go +++ b/api/v1beta1/allowed_list.go @@ -1,5 +1,6 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 + //nolint:dupl package v1beta1 diff --git a/api/v1beta1/deny_wildcard.go b/api/v1beta1/deny_wildcard.go index b6084977..10528f5e 100644 --- a/api/v1beta1/deny_wildcard.go +++ b/api/v1beta1/deny_wildcard.go @@ -4,11 +4,11 @@ package v1beta1 const ( - denyWildcard = "capsule.clastix.io/deny-wildcard" + DenyWildcard = "capsule.clastix.io/deny-wildcard" ) func (t *Tenant) IsWildcardDenied() bool { - if v, ok := t.Annotations[denyWildcard]; ok && v == "true" { + if v, ok := t.Annotations[DenyWildcard]; ok && v == "true" { return true } diff --git a/api/v1beta1/forbidden_list.go b/api/v1beta1/forbidden_list.go index 0b02d75d..7816dd5b 100644 --- a/api/v1beta1/forbidden_list.go +++ b/api/v1beta1/forbidden_list.go @@ -1,5 +1,6 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 + //nolint:dupl package v1beta1 diff --git a/api/v1beta2/additional_metadata.go b/api/v1beta2/additional_metadata.go new file mode 100644 index 00000000..9d708e73 --- /dev/null +++ b/api/v1beta2/additional_metadata.go @@ -0,0 +1,9 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type AdditionalMetadataSpec struct { + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` +} diff --git a/api/v1beta2/additional_role_bindings.go b/api/v1beta2/additional_role_bindings.go new file mode 100644 index 00000000..298aa8bd --- /dev/null +++ b/api/v1beta2/additional_role_bindings.go @@ -0,0 +1,12 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import rbacv1 "k8s.io/api/rbac/v1" + +type AdditionalRoleBindingsSpec struct { + ClusterRoleName string `json:"clusterRoleName"` + // kubebuilder:validation:Minimum=1 + Subjects []rbacv1.Subject `json:"subjects"` +} diff --git a/api/v1beta2/allowed_list.go b/api/v1beta2/allowed_list.go new file mode 100644 index 00000000..33e02c46 --- /dev/null +++ b/api/v1beta2/allowed_list.go @@ -0,0 +1,37 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "regexp" + "sort" + "strings" +) + +type AllowedListSpec struct { + Exact []string `json:"allowed,omitempty"` + Regex string `json:"allowedRegex,omitempty"` +} + +func (in *AllowedListSpec) ExactMatch(value string) (ok bool) { + if len(in.Exact) > 0 { + sort.SliceStable(in.Exact, func(i, j int) bool { + return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) + }) + + i := sort.SearchStrings(in.Exact, value) + + ok = i < len(in.Exact) && in.Exact[i] == value + } + + return +} + +func (in *AllowedListSpec) RegexMatch(value string) (ok bool) { + if len(in.Regex) > 0 { + ok = regexp.MustCompile(in.Regex).MatchString(value) + } + + return +} diff --git a/api/v1beta2/allowed_list_test.go b/api/v1beta2/allowed_list_test.go new file mode 100644 index 00000000..77754933 --- /dev/null +++ b/api/v1beta2/allowed_list_test.go @@ -0,0 +1,73 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 +//nolint:dupl +package v1beta2 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAllowedListSpec_ExactMatch(t *testing.T) { + type tc struct { + In []string + True []string + False []string + } + + for _, tc := range []tc{ + { + []string{"foo", "bar", "bizz", "buzz"}, + []string{"foo", "bar", "bizz", "buzz"}, + []string{"bing", "bong"}, + }, + { + []string{"one", "two", "three"}, + []string{"one", "two", "three"}, + []string{"a", "b", "c"}, + }, + { + nil, + nil, + []string{"any", "value"}, + }, + } { + a := AllowedListSpec{ + Exact: tc.In, + } + + for _, ok := range tc.True { + assert.True(t, a.ExactMatch(ok)) + } + + for _, ko := range tc.False { + assert.False(t, a.ExactMatch(ko)) + } + } +} + +func TestAllowedListSpec_RegexMatch(t *testing.T) { + type tc struct { + Regex string + True []string + False []string + } + + for _, tc := range []tc{ + {`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}}, + {``, nil, []string{"any", "value"}}, + } { + a := AllowedListSpec{ + Regex: tc.Regex, + } + + for _, ok := range tc.True { + assert.True(t, a.RegexMatch(ok)) + } + + for _, ko := range tc.False { + assert.False(t, a.RegexMatch(ko)) + } + } +} diff --git a/api/v1beta2/capsuleconfiguration_funcs.go b/api/v1beta2/capsuleconfiguration_funcs.go new file mode 100644 index 00000000..7e029cad --- /dev/null +++ b/api/v1beta2/capsuleconfiguration_funcs.go @@ -0,0 +1,131 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strconv" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + + capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" +) + +func (in *CapsuleConfiguration) ConvertTo(raw conversion.Hub) error { + dst, ok := raw.(*capsulev1alpha1.CapsuleConfiguration) + if !ok { + return fmt.Errorf("expected type *capsulev1alpha1.CapsuleConfiguration, got %T", dst) + } + + dst.ObjectMeta = in.ObjectMeta + dst.Spec.ProtectedNamespaceRegexpString = in.Spec.ProtectedNamespaceRegexpString + dst.Spec.UserGroups = in.Spec.UserGroups + dst.Spec.ProtectedNamespaceRegexpString = in.Spec.ProtectedNamespaceRegexpString + + annotations := dst.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + + if in.Spec.NodeMetadata != nil { + annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenLabels.Exact, ",") + annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenLabels.Regex + annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact, ",") + annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenAnnotations.Regex + } + + annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName] = fmt.Sprintf("%t", in.Spec.EnableTLSReconciler) + annotations[capsulev1alpha1.TLSSecretNameAnnotation] = in.Spec.CapsuleResources.TLSSecretName + annotations[capsulev1alpha1.MutatingWebhookConfigurationName] = in.Spec.CapsuleResources.MutatingWebhookConfigurationName + annotations[capsulev1alpha1.ValidatingWebhookConfigurationName] = in.Spec.CapsuleResources.ValidatingWebhookConfigurationName + + dst.SetAnnotations(annotations) + + return nil +} + +func (in *CapsuleConfiguration) ConvertFrom(raw conversion.Hub) error { + src, ok := raw.(*capsulev1alpha1.CapsuleConfiguration) + if !ok { + return fmt.Errorf("expected type *capsulev1alpha1.CapsuleConfiguration, got %T", src) + } + + in.ObjectMeta = src.ObjectMeta + in.Spec.ProtectedNamespaceRegexpString = src.Spec.ProtectedNamespaceRegexpString + in.Spec.UserGroups = src.Spec.UserGroups + in.Spec.ProtectedNamespaceRegexpString = src.Spec.ProtectedNamespaceRegexpString + + annotations := src.GetAnnotations() + + if value, found := annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation]; found { + if in.Spec.NodeMetadata == nil { + in.Spec.NodeMetadata = &NodeMetadata{} + } + + in.Spec.NodeMetadata.ForbiddenLabels.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1alpha1.ForbiddenNodeLabelsAnnotation) + } + + if value, found := annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation]; found { + if in.Spec.NodeMetadata == nil { + in.Spec.NodeMetadata = &NodeMetadata{} + } + + in.Spec.NodeMetadata.ForbiddenLabels.Regex = value + + delete(annotations, capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation) + } + + if value, found := annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation]; found { + if in.Spec.NodeMetadata == nil { + in.Spec.NodeMetadata = &NodeMetadata{} + } + + in.Spec.NodeMetadata.ForbiddenAnnotations.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation) + } + + if value, found := annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation]; found { + if in.Spec.NodeMetadata == nil { + in.Spec.NodeMetadata = &NodeMetadata{} + } + + in.Spec.NodeMetadata.ForbiddenAnnotations.Regex = value + + delete(annotations, capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation) + } + + if value, found := annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName]; found { + v, _ := strconv.ParseBool(value) + + in.Spec.EnableTLSReconciler = v + + delete(annotations, capsulev1alpha1.EnableTLSConfigurationAnnotationName) + } + + if value, found := annotations[capsulev1alpha1.TLSSecretNameAnnotation]; found { + in.Spec.CapsuleResources.TLSSecretName = value + + delete(annotations, capsulev1alpha1.TLSSecretNameAnnotation) + } + + if value, found := annotations[capsulev1alpha1.MutatingWebhookConfigurationName]; found { + in.Spec.CapsuleResources.MutatingWebhookConfigurationName = value + + delete(annotations, capsulev1alpha1.MutatingWebhookConfigurationName) + } + + if value, found := annotations[capsulev1alpha1.ValidatingWebhookConfigurationName]; found { + in.Spec.CapsuleResources.ValidatingWebhookConfigurationName = value + + delete(annotations, capsulev1alpha1.ValidatingWebhookConfigurationName) + } + + in.SetAnnotations(annotations) + + return nil +} diff --git a/api/v1beta2/capsuleconfiguration_types.go b/api/v1beta2/capsuleconfiguration_types.go new file mode 100644 index 00000000..207cd00b --- /dev/null +++ b/api/v1beta2/capsuleconfiguration_types.go @@ -0,0 +1,75 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CapsuleConfigurationSpec defines the Capsule configuration. +type CapsuleConfigurationSpec struct { + // Names of the groups for Capsule users. + // +kubebuilder:default={capsule.clastix.io} + UserGroups []string `json:"userGroups,omitempty"` + // Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, + // separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. + // +kubebuilder:default=false + ForceTenantPrefix bool `json:"forceTenantPrefix,omitempty"` + // Disallow creation of namespaces, whose name matches this regexp + ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"` + // Allows to set different name rather than the canonical one for the Capsule configuration objects, + // such as webhook secret or configurations. + CapsuleResources CapsuleResources `json:"overrides"` + // Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. + // This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes. + NodeMetadata *NodeMetadata `json:"nodeMetadata"` + // Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks + // when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager. + // +kubebuilder:default=true + EnableTLSReconciler bool `json:"enableTLSReconciler"` //nolint:tagliatelle +} + +type NodeMetadata struct { + // Define the labels that a Tenant Owner cannot set for their nodes. + ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels"` + // Define the annotations that a Tenant Owner cannot set for their nodes. + ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations"` +} + +type CapsuleResources struct { + // Defines the Secret name used for the webhook server. + // Must be in the same Namespace where the Capsule Deployment is deployed. + // +kubebuilder:default=capsule-tls + TLSSecretName string `json:"TLSSecretName"` //nolint:tagliatelle + // Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + // +kubebuilder:default=capsule-mutating-webhook-configuration + MutatingWebhookConfigurationName string `json:"mutatingWebhookConfigurationName"` + // Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + // +kubebuilder:default=capsule-validating-webhook-configuration + ValidatingWebhookConfigurationName string `json:"validatingWebhookConfigurationName"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster + +// CapsuleConfiguration is the Schema for the Capsule configuration API. +type CapsuleConfiguration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec CapsuleConfigurationSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// CapsuleConfigurationList contains a list of CapsuleConfiguration. +type CapsuleConfigurationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CapsuleConfiguration `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CapsuleConfiguration{}, &CapsuleConfigurationList{}) +} diff --git a/api/v1beta2/conversion_hub.go b/api/v1beta2/conversion_hub.go new file mode 100644 index 00000000..0c57e00c --- /dev/null +++ b/api/v1beta2/conversion_hub.go @@ -0,0 +1,401 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +//nolint:nestif,cyclop,maintidx +package v1beta2 + +import ( + "fmt" + "strconv" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" +) + +//nolint:gocyclo +func (in *Tenant) ConvertFrom(raw conversion.Hub) error { + src, ok := raw.(*capsulev1beta1.Tenant) + if !ok { + return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) + } + + annotations := src.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + in.ObjectMeta = src.ObjectMeta + in.Spec.Owners = make(OwnerListSpec, 0, len(src.Spec.Owners)) + + for index, owner := range src.Spec.Owners { + proxySettings := make([]ProxySettings, 0, len(owner.ProxyOperations)) + + for _, proxyOp := range owner.ProxyOperations { + ops := make([]ProxyOperation, 0, len(proxyOp.Operations)) + + for _, op := range proxyOp.Operations { + ops = append(ops, ProxyOperation(op)) + } + + proxySettings = append(proxySettings, ProxySettings{ + Kind: ProxyServiceKind(proxyOp.Kind), + Operations: ops, + }) + } + + in.Spec.Owners = append(in.Spec.Owners, OwnerSpec{ + Kind: OwnerKind(owner.Kind), + Name: owner.Name, + ClusterRoles: owner.GetRoles(*src, index), + ProxyOperations: proxySettings, + }) + } + + if nsOpts := src.Spec.NamespaceOptions; nsOpts != nil { + in.Spec.NamespaceOptions = &NamespaceOptions{} + + in.Spec.NamespaceOptions.Quota = src.Spec.NamespaceOptions.Quota + + if nsOpts.AdditionalMetadata != nil { + in.Spec.NamespaceOptions.AdditionalMetadata = &AdditionalMetadataSpec{} + + in.Spec.NamespaceOptions.AdditionalMetadata.Annotations = nsOpts.AdditionalMetadata.Annotations + in.Spec.NamespaceOptions.AdditionalMetadata.Labels = nsOpts.AdditionalMetadata.Labels + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenLabels.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenLabels.Regex = value + + delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenAnnotations.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenAnnotations.Regex = value + + delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation) + } + } + + if svcOpts := src.Spec.ServiceOptions; svcOpts != nil { + in.Spec.ServiceOptions = &ServiceOptions{} + + if metadata := svcOpts.AdditionalMetadata; metadata != nil { + in.Spec.ServiceOptions.AdditionalMetadata = &AdditionalMetadataSpec{ + Labels: metadata.Labels, + Annotations: metadata.Annotations, + } + } + + if types := svcOpts.AllowedServices; types != nil { + in.Spec.ServiceOptions.AllowedServices = &AllowedServices{ + NodePort: types.NodePort, + ExternalName: types.ExternalName, + LoadBalancer: types.LoadBalancer, + } + } + + if externalIPs := svcOpts.ExternalServiceIPs; externalIPs != nil { + allowed := make([]AllowedIP, 0, len(externalIPs.Allowed)) + + for _, ip := range externalIPs.Allowed { + allowed = append(allowed, AllowedIP(ip)) + } + + in.Spec.ServiceOptions.ExternalServiceIPs = &ExternalServiceIPsSpec{ + Allowed: allowed, + } + } + } + + if sc := src.Spec.StorageClasses; sc != nil { + in.Spec.StorageClasses = &AllowedListSpec{ + Exact: sc.Exact, + Regex: sc.Regex, + } + } + + if scope := src.Spec.IngressOptions.HostnameCollisionScope; len(scope) > 0 { + in.Spec.IngressOptions.HostnameCollisionScope = HostnameCollisionScope(scope) + } + + v, found := annotations[capsulev1beta1.DenyWildcard] + if found { + value, err := strconv.ParseBool(v) + if err == nil { + in.Spec.IngressOptions.AllowWildcardHostnames = value + + delete(annotations, capsulev1beta1.DenyWildcard) + } + } + + if ingressClass := src.Spec.IngressOptions.AllowedClasses; ingressClass != nil { + in.Spec.IngressOptions.AllowedClasses = &AllowedListSpec{ + Exact: ingressClass.Exact, + Regex: ingressClass.Regex, + } + } + + if hostnames := src.Spec.IngressOptions.AllowedHostnames; hostnames != nil { + in.Spec.IngressOptions.AllowedClasses = &AllowedListSpec{ + Exact: hostnames.Exact, + Regex: hostnames.Regex, + } + } + + if allowed := src.Spec.ContainerRegistries; allowed != nil { + in.Spec.ContainerRegistries = &AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + in.Spec.NodeSelector = src.Spec.NodeSelector + + if items := src.Spec.NetworkPolicies.Items; len(items) > 0 { + in.Spec.NetworkPolicies.Items = items + } + + if items := src.Spec.LimitRanges.Items; len(items) > 0 { + in.Spec.LimitRanges.Items = items + } + + if scope := src.Spec.ResourceQuota.Scope; len(scope) > 0 { + in.Spec.ResourceQuota.Scope = ResourceQuotaScope(scope) + } + + if items := src.Spec.ResourceQuota.Items; len(items) > 0 { + in.Spec.ResourceQuota.Items = items + } + + in.Spec.AdditionalRoleBindings = make([]AdditionalRoleBindingsSpec, 0, len(src.Spec.AdditionalRoleBindings)) + for _, rb := range src.Spec.AdditionalRoleBindings { + in.Spec.AdditionalRoleBindings = append(in.Spec.AdditionalRoleBindings, AdditionalRoleBindingsSpec{ + ClusterRoleName: rb.ClusterRoleName, + Subjects: rb.Subjects, + }) + } + + in.Spec.ImagePullPolicies = make([]ImagePullPolicySpec, 0, len(src.Spec.ImagePullPolicies)) + for _, policy := range src.Spec.ImagePullPolicies { + in.Spec.ImagePullPolicies = append(in.Spec.ImagePullPolicies, ImagePullPolicySpec(policy)) + } + + if allowed := src.Spec.PriorityClasses; allowed != nil { + in.Spec.PriorityClasses = &AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + if v, found := annotations["capsule.clastix.io/cordon"]; found { + value, err := strconv.ParseBool(v) + if err == nil { + delete(annotations, "capsule.clastix.io/cordon") + } + + in.Spec.Cordoned = value + } + + if _, found := annotations[capsulev1beta1.ProtectedTenantAnnotation]; found { + in.Spec.PreventDeletion = true + + delete(annotations, capsulev1beta1.ProtectedTenantAnnotation) + } + + in.SetAnnotations(annotations) + + in.Status.Namespaces = src.Status.Namespaces + in.Status.Size = src.Status.Size + in.Status.State = tenantState(src.Status.State) + + return nil +} + +func (in *Tenant) ConvertTo(raw conversion.Hub) error { + dst, ok := raw.(*capsulev1beta1.Tenant) + if !ok { + return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) + } + + annotations := in.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + dst.ObjectMeta = in.ObjectMeta + dst.Spec.Owners = make(capsulev1beta1.OwnerListSpec, 0, len(in.Spec.Owners)) + + for index, owner := range in.Spec.Owners { + proxySettings := make([]capsulev1beta1.ProxySettings, 0, len(owner.ProxyOperations)) + + for _, proxyOp := range owner.ProxyOperations { + ops := make([]capsulev1beta1.ProxyOperation, 0, len(proxyOp.Operations)) + + for _, op := range proxyOp.Operations { + ops = append(ops, capsulev1beta1.ProxyOperation(op)) + } + + proxySettings = append(proxySettings, capsulev1beta1.ProxySettings{ + Kind: capsulev1beta1.ProxyServiceKind(proxyOp.Kind), + Operations: ops, + }) + } + + dst.Spec.Owners = append(dst.Spec.Owners, capsulev1beta1.OwnerSpec{ + Kind: capsulev1beta1.OwnerKind(owner.Kind), + Name: owner.Name, + ProxyOperations: proxySettings, + }) + + if clusterRoles := owner.ClusterRoles; len(clusterRoles) > 0 { + annotations[fmt.Sprintf("%s/%d", capsulev1beta1.ClusterRoleNamesAnnotation, index)] = strings.Join(owner.ClusterRoles, ",") + } + } + + if nsOpts := in.Spec.NamespaceOptions; nsOpts != nil { + dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{} + + if quota := nsOpts.Quota; quota != nil { + dst.Spec.NamespaceOptions.Quota = quota + } + + if metadata := nsOpts.AdditionalMetadata; metadata != nil { + dst.Spec.NamespaceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{ + Labels: metadata.Labels, + Annotations: metadata.Annotations, + } + } + + if exact := nsOpts.ForbiddenAnnotations.Exact; len(exact) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",") + } + + if regex := nsOpts.ForbiddenAnnotations.Regex; len(regex) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex + } + + if exact := nsOpts.ForbiddenLabels.Exact; len(exact) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",") + } + + if regex := nsOpts.ForbiddenLabels.Regex; len(regex) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation] = regex + } + } + + if svcOpts := in.Spec.ServiceOptions; svcOpts != nil { + dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} + + if metadata := svcOpts.AdditionalMetadata; metadata != nil { + dst.Spec.ServiceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{ + Labels: metadata.Labels, + Annotations: metadata.Annotations, + } + } + + if allowed := svcOpts.AllowedServices; allowed != nil { + dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{ + NodePort: allowed.NodePort, + ExternalName: allowed.ExternalName, + LoadBalancer: allowed.ExternalName, + } + } + + if externalIPs := svcOpts.ExternalServiceIPs; externalIPs != nil { + allowed := make([]capsulev1beta1.AllowedIP, 0, len(externalIPs.Allowed)) + + for _, ip := range externalIPs.Allowed { + allowed = append(allowed, capsulev1beta1.AllowedIP(ip)) + } + + dst.Spec.ServiceOptions.ExternalServiceIPs = &capsulev1beta1.ExternalServiceIPsSpec{ + Allowed: allowed, + } + } + } + + if storageClass := in.Spec.StorageClasses; storageClass != nil { + dst.Spec.StorageClasses = &capsulev1beta1.AllowedListSpec{ + Exact: storageClass.Exact, + Regex: storageClass.Regex, + } + } + + dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScope(in.Spec.IngressOptions.HostnameCollisionScope) + + if allowed := in.Spec.IngressOptions.AllowedClasses; allowed != nil { + dst.Spec.IngressOptions.AllowedClasses = &capsulev1beta1.AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + if allowed := in.Spec.IngressOptions.AllowedHostnames; allowed != nil { + dst.Spec.IngressOptions.AllowedHostnames = &capsulev1beta1.AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + annotations[capsulev1beta1.DenyWildcard] = fmt.Sprintf("%t", in.Spec.IngressOptions.AllowWildcardHostnames) + + if allowed := in.Spec.ContainerRegistries; allowed != nil { + dst.Spec.ContainerRegistries = &capsulev1beta1.AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + dst.Spec.NodeSelector = in.Spec.NodeSelector + + dst.Spec.NetworkPolicies = capsulev1beta1.NetworkPolicySpec{ + Items: in.Spec.NetworkPolicies.Items, + } + + dst.Spec.LimitRanges = capsulev1beta1.LimitRangesSpec{ + Items: in.Spec.LimitRanges.Items, + } + + dst.Spec.ResourceQuota = capsulev1beta1.ResourceQuotaSpec{ + Scope: capsulev1beta1.ResourceQuotaScope(in.Spec.ResourceQuota.Scope), + Items: in.Spec.ResourceQuota.Items, + } + + dst.Spec.AdditionalRoleBindings = make([]capsulev1beta1.AdditionalRoleBindingsSpec, 0, len(in.Spec.AdditionalRoleBindings)) + for _, item := range in.Spec.AdditionalRoleBindings { + dst.Spec.AdditionalRoleBindings = append(dst.Spec.AdditionalRoleBindings, capsulev1beta1.AdditionalRoleBindingsSpec{ + ClusterRoleName: item.ClusterRoleName, + Subjects: item.Subjects, + }) + } + + dst.Spec.ImagePullPolicies = make([]capsulev1beta1.ImagePullPolicySpec, 0, len(in.Spec.ImagePullPolicies)) + for _, item := range in.Spec.ImagePullPolicies { + dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, capsulev1beta1.ImagePullPolicySpec(item)) + } + + if allowed := in.Spec.PriorityClasses; allowed != nil { + dst.Spec.PriorityClasses = &capsulev1beta1.AllowedListSpec{ + Exact: allowed.Exact, + Regex: allowed.Regex, + } + } + + dst.SetAnnotations(annotations) + + return nil +} diff --git a/api/v1beta2/custom_resource_quota.go b/api/v1beta2/custom_resource_quota.go new file mode 100644 index 00000000..42ccb730 --- /dev/null +++ b/api/v1beta2/custom_resource_quota.go @@ -0,0 +1,59 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strconv" +) + +const ( + ResourceQuotaAnnotationPrefix = "quota.resources.capsule.clastix.io" + ResourceUsedAnnotationPrefix = "used.resources.capsule.clastix.io" +) + +func UsedAnnotationForResource(kindGroup string) string { + return fmt.Sprintf("%s/%s", ResourceUsedAnnotationPrefix, kindGroup) +} + +func LimitAnnotationForResource(kindGroup string) string { + return fmt.Sprintf("%s/%s", ResourceQuotaAnnotationPrefix, kindGroup) +} + +func GetUsedResourceFromTenant(tenant Tenant, kindGroup string) (int64, error) { + usedStr, ok := tenant.GetAnnotations()[UsedAnnotationForResource(kindGroup)] + if !ok { + usedStr = "0" + } + + used, _ := strconv.ParseInt(usedStr, 10, 10) + + return used, nil +} + +type NonLimitedResourceError struct { + kindGroup string +} + +func NewNonLimitedResourceError(kindGroup string) *NonLimitedResourceError { + return &NonLimitedResourceError{kindGroup: kindGroup} +} + +func (n NonLimitedResourceError) Error() string { + return fmt.Sprintf("resource %s is not limited for the current tenant", n.kindGroup) +} + +func GetLimitResourceFromTenant(tenant Tenant, kindGroup string) (int64, error) { + limitStr, ok := tenant.GetAnnotations()[LimitAnnotationForResource(kindGroup)] + if !ok { + return 0, NewNonLimitedResourceError(kindGroup) + } + + limit, err := strconv.ParseInt(limitStr, 10, 10) + if err != nil { + return 0, fmt.Errorf("resource %s limit cannot be parsed, %w", kindGroup, err) + } + + return limit, nil +} diff --git a/api/v1beta2/forbidden_list.go b/api/v1beta2/forbidden_list.go new file mode 100644 index 00000000..65c8e74e --- /dev/null +++ b/api/v1beta2/forbidden_list.go @@ -0,0 +1,37 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "regexp" + "sort" + "strings" +) + +type ForbiddenListSpec struct { + Exact []string `json:"denied,omitempty"` + Regex string `json:"deniedRegex,omitempty"` +} + +func (in *ForbiddenListSpec) ExactMatch(value string) (ok bool) { + if len(in.Exact) > 0 { + sort.SliceStable(in.Exact, func(i, j int) bool { + return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) + }) + + i := sort.SearchStrings(in.Exact, value) + + ok = i < len(in.Exact) && in.Exact[i] == value + } + + return +} + +func (in ForbiddenListSpec) RegexMatch(value string) (ok bool) { + if len(in.Regex) > 0 { + ok = regexp.MustCompile(in.Regex).MatchString(value) + } + + return +} diff --git a/api/v1beta2/forbidden_list_test.go b/api/v1beta2/forbidden_list_test.go new file mode 100644 index 00000000..30bc9ac0 --- /dev/null +++ b/api/v1beta2/forbidden_list_test.go @@ -0,0 +1,73 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 +//nolint:dupl +package v1beta2 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestForbiddenListSpec_ExactMatch(t *testing.T) { + type tc struct { + In []string + True []string + False []string + } + + for _, tc := range []tc{ + { + []string{"foo", "bar", "bizz", "buzz"}, + []string{"foo", "bar", "bizz", "buzz"}, + []string{"bing", "bong"}, + }, + { + []string{"one", "two", "three"}, + []string{"one", "two", "three"}, + []string{"a", "b", "c"}, + }, + { + nil, + nil, + []string{"any", "value"}, + }, + } { + a := ForbiddenListSpec{ + Exact: tc.In, + } + + for _, ok := range tc.True { + assert.True(t, a.ExactMatch(ok)) + } + + for _, ko := range tc.False { + assert.False(t, a.ExactMatch(ko)) + } + } +} + +func TestForbiddenListSpec_RegexMatch(t *testing.T) { + type tc struct { + Regex string + True []string + False []string + } + + for _, tc := range []tc{ + {`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}}, + {``, nil, []string{"any", "value"}}, + } { + a := ForbiddenListSpec{ + Regex: tc.Regex, + } + + for _, ok := range tc.True { + assert.True(t, a.RegexMatch(ok)) + } + + for _, ko := range tc.False { + assert.False(t, a.RegexMatch(ko)) + } + } +} diff --git a/api/v1beta2/groupversion_info.go b/api/v1beta2/groupversion_info.go new file mode 100644 index 00000000..3117d80f --- /dev/null +++ b/api/v1beta2/groupversion_info.go @@ -0,0 +1,23 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +// Package v1beta2 contains API Schema definitions for the capsule v1beta2 API group +//+kubebuilder:object:generate=true +//+groupName=capsule.clastix.io +package v1beta2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects. + GroupVersion = schema.GroupVersion{Group: "capsule.clastix.io", Version: "v1beta2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/v1beta2/hostname_collision_scope.go b/api/v1beta2/hostname_collision_scope.go new file mode 100644 index 00000000..a46ab934 --- /dev/null +++ b/api/v1beta2/hostname_collision_scope.go @@ -0,0 +1,14 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +const ( + HostnameCollisionScopeCluster HostnameCollisionScope = "Cluster" + HostnameCollisionScopeTenant HostnameCollisionScope = "Tenant" + HostnameCollisionScopeNamespace HostnameCollisionScope = "Namespace" + HostnameCollisionScopeDisabled HostnameCollisionScope = "Disabled" +) + +// +kubebuilder:validation:Enum=Cluster;Tenant;Namespace;Disabled +type HostnameCollisionScope string diff --git a/api/v1beta2/image_pull_policy.go b/api/v1beta2/image_pull_policy.go new file mode 100644 index 00000000..cb9e8e2f --- /dev/null +++ b/api/v1beta2/image_pull_policy.go @@ -0,0 +1,11 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +// +kubebuilder:validation:Enum=Always;Never;IfNotPresent +type ImagePullPolicySpec string + +func (i ImagePullPolicySpec) String() string { + return string(i) +} diff --git a/api/v1beta2/ingress_options.go b/api/v1beta2/ingress_options.go new file mode 100644 index 00000000..d55c0cfd --- /dev/null +++ b/api/v1beta2/ingress_options.go @@ -0,0 +1,26 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type IngressOptions struct { + // Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + AllowedClasses *AllowedListSpec `json:"allowedClasses,omitempty"` + // Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. + // + // + // - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. + // + // - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. + // + // - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. + // + // + // Optional. + // +kubebuilder:default=Disabled + HostnameCollisionScope HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"` + // Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + AllowedHostnames *AllowedListSpec `json:"allowedHostnames,omitempty"` + // Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. + AllowWildcardHostnames bool `json:"allowWildcardHostnames"` +} diff --git a/api/v1beta2/limit_ranges.go b/api/v1beta2/limit_ranges.go new file mode 100644 index 00000000..cd5d0a13 --- /dev/null +++ b/api/v1beta2/limit_ranges.go @@ -0,0 +1,10 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import corev1 "k8s.io/api/core/v1" + +type LimitRangesSpec struct { + Items []corev1.LimitRangeSpec `json:"items,omitempty"` +} diff --git a/api/v1beta2/namespace_options.go b/api/v1beta2/namespace_options.go new file mode 100644 index 00000000..10d7e906 --- /dev/null +++ b/api/v1beta2/namespace_options.go @@ -0,0 +1,16 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type NamespaceOptions struct { + //+kubebuilder:validation:Minimum=1 + // Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + Quota *int32 `json:"quota,omitempty"` + // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + // Define the labels that a Tenant Owner cannot set for their Namespace resources. + ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels"` + // Define the annotations that a Tenant Owner cannot set for their Namespace resources. + ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations"` +} diff --git a/api/v1beta2/network_policy.go b/api/v1beta2/network_policy.go new file mode 100644 index 00000000..67668129 --- /dev/null +++ b/api/v1beta2/network_policy.go @@ -0,0 +1,12 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + networkingv1 "k8s.io/api/networking/v1" +) + +type NetworkPolicySpec struct { + Items []networkingv1.NetworkPolicySpec `json:"items,omitempty"` +} diff --git a/api/v1beta2/owner.go b/api/v1beta2/owner.go new file mode 100644 index 00000000..7ae29010 --- /dev/null +++ b/api/v1beta2/owner.go @@ -0,0 +1,57 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type OwnerSpec struct { + // Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + Kind OwnerKind `json:"kind"` + // Name of tenant owner. + Name string `json:"name"` + // Defines additional cluster-roles for the specific Owner. + // +kubebuilder:default={admin,capsule-namespace-deleter} + ClusterRoles []string `json:"clusterRoles,omitempty"` + // Proxy settings for tenant owner. + ProxyOperations []ProxySettings `json:"proxySettings,omitempty"` +} + +// +kubebuilder:validation:Enum=User;Group;ServiceAccount +type OwnerKind string + +func (k OwnerKind) String() string { + return string(k) +} + +type ProxySettings struct { + Kind ProxyServiceKind `json:"kind"` + Operations []ProxyOperation `json:"operations"` +} + +// +kubebuilder:validation:Enum=List;Update;Delete +type ProxyOperation string + +func (p ProxyOperation) String() string { + return string(p) +} + +// +kubebuilder:validation:Enum=Nodes;StorageClasses;IngressClasses;PriorityClasses +type ProxyServiceKind string + +func (p ProxyServiceKind) String() string { + return string(p) +} + +const ( + NodesProxy ProxyServiceKind = "Nodes" + StorageClassesProxy ProxyServiceKind = "StorageClasses" + IngressClassesProxy ProxyServiceKind = "IngressClasses" + PriorityClassesProxy ProxyServiceKind = "PriorityClasses" + + ListOperation ProxyOperation = "List" + UpdateOperation ProxyOperation = "Update" + DeleteOperation ProxyOperation = "Delete" + + UserOwner OwnerKind = "User" + GroupOwner OwnerKind = "Group" + ServiceAccountOwner OwnerKind = "ServiceAccount" +) diff --git a/api/v1beta2/owner_list.go b/api/v1beta2/owner_list.go new file mode 100644 index 00000000..3677acc6 --- /dev/null +++ b/api/v1beta2/owner_list.go @@ -0,0 +1,41 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "sort" +) + +type OwnerListSpec []OwnerSpec + +func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) { + sort.Sort(ByKindAndName(o)) + i := sort.Search(len(o), func(i int) bool { + return o[i].Kind >= kind && o[i].Name >= name + }) + + if i < len(o) && o[i].Kind == kind && o[i].Name == name { + return o[i] + } + + return +} + +type ByKindAndName OwnerListSpec + +func (b ByKindAndName) Len() int { + return len(b) +} + +func (b ByKindAndName) Less(i, j int) bool { + if b[i].Kind.String() != b[j].Kind.String() { + return b[i].Kind.String() < b[j].Kind.String() + } + + return b[i].Name < b[j].Name +} + +func (b ByKindAndName) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} diff --git a/api/v1beta2/owner_list_test.go b/api/v1beta2/owner_list_test.go new file mode 100644 index 00000000..93697b93 --- /dev/null +++ b/api/v1beta2/owner_list_test.go @@ -0,0 +1,86 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOwnerListSpec_FindOwner(t *testing.T) { + bla := OwnerSpec{ + Kind: UserOwner, + Name: "bla", + ProxyOperations: []ProxySettings{ + { + Kind: IngressClassesProxy, + Operations: []ProxyOperation{"Delete"}, + }, + }, + } + bar := OwnerSpec{ + Kind: GroupOwner, + Name: "bar", + ProxyOperations: []ProxySettings{ + { + Kind: StorageClassesProxy, + Operations: []ProxyOperation{"Delete"}, + }, + }, + } + baz := OwnerSpec{ + Kind: UserOwner, + Name: "baz", + ProxyOperations: []ProxySettings{ + { + Kind: StorageClassesProxy, + Operations: []ProxyOperation{"Update"}, + }, + }, + } + fim := OwnerSpec{ + Kind: ServiceAccountOwner, + Name: "fim", + ProxyOperations: []ProxySettings{ + { + Kind: NodesProxy, + Operations: []ProxyOperation{"List"}, + }, + }, + } + bom := OwnerSpec{ + Kind: GroupOwner, + Name: "bom", + ProxyOperations: []ProxySettings{ + { + Kind: StorageClassesProxy, + Operations: []ProxyOperation{"Delete"}, + }, + { + Kind: NodesProxy, + Operations: []ProxyOperation{"Delete"}, + }, + }, + } + qip := OwnerSpec{ + Kind: ServiceAccountOwner, + Name: "qip", + ProxyOperations: []ProxySettings{ + { + Kind: StorageClassesProxy, + Operations: []ProxyOperation{"List", "Delete"}, + }, + }, + } + owners := OwnerListSpec{bom, qip, bla, bar, baz, fim} + + assert.Equal(t, owners.FindOwner("bom", GroupOwner), bom) + assert.Equal(t, owners.FindOwner("qip", ServiceAccountOwner), qip) + assert.Equal(t, owners.FindOwner("bla", UserOwner), bla) + assert.Equal(t, owners.FindOwner("bar", GroupOwner), bar) + assert.Equal(t, owners.FindOwner("baz", UserOwner), baz) + assert.Equal(t, owners.FindOwner("fim", ServiceAccountOwner), fim) + assert.Equal(t, owners.FindOwner("notfound", ServiceAccountOwner), OwnerSpec{}) +} diff --git a/api/v1beta2/resource_quota.go b/api/v1beta2/resource_quota.go new file mode 100644 index 00000000..80534c91 --- /dev/null +++ b/api/v1beta2/resource_quota.go @@ -0,0 +1,21 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import corev1 "k8s.io/api/core/v1" + +// +kubebuilder:validation:Enum=Tenant;Namespace +type ResourceQuotaScope string + +const ( + ResourceQuotaScopeTenant ResourceQuotaScope = "Tenant" + ResourceQuotaScopeNamespace ResourceQuotaScope = "Namespace" +) + +type ResourceQuotaSpec struct { + // +kubebuilder:default=Tenant + // Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + Scope ResourceQuotaScope `json:"scope,omitempty"` + Items []corev1.ResourceQuotaSpec `json:"items,omitempty"` +} diff --git a/api/v1beta2/service_allowed_ips.go b/api/v1beta2/service_allowed_ips.go new file mode 100644 index 00000000..9eb6247e --- /dev/null +++ b/api/v1beta2/service_allowed_ips.go @@ -0,0 +1,11 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" +type AllowedIP string + +type ExternalServiceIPsSpec struct { + Allowed []AllowedIP `json:"allowed"` +} diff --git a/api/v1beta2/service_allowed_types.go b/api/v1beta2/service_allowed_types.go new file mode 100644 index 00000000..1f4abd4e --- /dev/null +++ b/api/v1beta2/service_allowed_types.go @@ -0,0 +1,16 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type AllowedServices struct { + //+kubebuilder:default=true + // Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + NodePort *bool `json:"nodePort,omitempty"` + //+kubebuilder:default=true + // Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + ExternalName *bool `json:"externalName,omitempty"` + //+kubebuilder:default=true + // Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + LoadBalancer *bool `json:"loadBalancer,omitempty"` +} diff --git a/api/v1beta2/service_options.go b/api/v1beta2/service_options.go new file mode 100644 index 00000000..a232aa5d --- /dev/null +++ b/api/v1beta2/service_options.go @@ -0,0 +1,13 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +type ServiceOptions struct { + // Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + // Block or deny certain type of Services. Optional. + AllowedServices *AllowedServices `json:"allowedServices,omitempty"` + // Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalIPs,omitempty"` +} diff --git a/api/v1beta2/tenant_annotations.go b/api/v1beta2/tenant_annotations.go new file mode 100644 index 00000000..79fde6b5 --- /dev/null +++ b/api/v1beta2/tenant_annotations.go @@ -0,0 +1,26 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strings" +) + +const ( + AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes" + AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp" + AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes" + AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp" + AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries" + AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp" +) + +func UsedQuotaFor(resource fmt.Stringer) string { + return "quota.capsule.clastix.io/used-" + strings.ReplaceAll(resource.String(), "/", "_") +} + +func HardQuotaFor(resource fmt.Stringer) string { + return "quota.capsule.clastix.io/hard-" + strings.ReplaceAll(resource.String(), "/", "_") +} diff --git a/api/v1beta2/tenant_func.go b/api/v1beta2/tenant_func.go new file mode 100644 index 00000000..166956aa --- /dev/null +++ b/api/v1beta2/tenant_func.go @@ -0,0 +1,38 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "sort" + + corev1 "k8s.io/api/core/v1" +) + +func (in *Tenant) IsFull() bool { + // we don't have limits on assigned Namespaces + if in.Spec.NamespaceOptions == nil || in.Spec.NamespaceOptions.Quota == nil { + return false + } + + return len(in.Status.Namespaces) >= int(*in.Spec.NamespaceOptions.Quota) +} + +func (in *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { + var l []string + + for _, ns := range namespaces { + if ns.Status.Phase == corev1.NamespaceActive { + l = append(l, ns.GetName()) + } + } + + sort.Strings(l) + + in.Status.Namespaces = l + in.Status.Size = uint(len(l)) +} + +func (in *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings { + return in.Spec.Owners.FindOwner(name, kind).ProxyOperations +} diff --git a/api/v1beta2/tenant_labels.go b/api/v1beta2/tenant_labels.go new file mode 100644 index 00000000..98d0e064 --- /dev/null +++ b/api/v1beta2/tenant_labels.go @@ -0,0 +1,32 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func GetTypeLabel(t runtime.Object) (label string, err error) { + switch v := t.(type) { + case *Tenant: + return "capsule.clastix.io/tenant", nil + case *corev1.LimitRange: + return "capsule.clastix.io/limit-range", nil + case *networkingv1.NetworkPolicy: + return "capsule.clastix.io/network-policy", nil + case *corev1.ResourceQuota: + return "capsule.clastix.io/resource-quota", nil + case *rbacv1.RoleBinding: + return "capsule.clastix.io/role-binding", nil + default: + err = fmt.Errorf("type %T is not mapped as Capsule label recognized", v) + } + + return +} diff --git a/api/v1beta2/tenant_status.go b/api/v1beta2/tenant_status.go new file mode 100644 index 00000000..de6b44dc --- /dev/null +++ b/api/v1beta2/tenant_status.go @@ -0,0 +1,23 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +// +kubebuilder:validation:Enum=Cordoned;Active +type tenantState string + +const ( + TenantStateActive tenantState = "Active" + TenantStateCordoned tenantState = "Cordoned" +) + +// Returns the observed state of the Tenant. +type TenantStatus struct { + //+kubebuilder:default=Active + // The operational state of the Tenant. Possible values are "Active", "Cordoned". + State tenantState `json:"state"` + // How many namespaces are assigned to the Tenant. + Size uint `json:"size"` + // List of namespaces assigned to the Tenant. + Namespaces []string `json:"namespaces,omitempty"` +} diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go new file mode 100644 index 00000000..adc0c66b --- /dev/null +++ b/api/v1beta2/tenant_types.go @@ -0,0 +1,74 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// TenantSpec defines the desired state of Tenant. +type TenantSpec struct { + // Specifies the owners of the Tenant. Mandatory. + Owners OwnerListSpec `json:"owners"` + // Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"` + // Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + ServiceOptions *ServiceOptions `json:"serviceOptions,omitempty"` + // Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"` + // Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + IngressOptions IngressOptions `json:"ingressOptions,omitempty"` + // Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"` + // Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + // Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + NetworkPolicies NetworkPolicySpec `json:"networkPolicies,omitempty"` + // Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + LimitRanges LimitRangesSpec `json:"limitRanges,omitempty"` + // Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + ResourceQuota ResourceQuotaSpec `json:"resourceQuotas,omitempty"` + // Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. + AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` + // Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. + ImagePullPolicies []ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` + // Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"` + // Toggling the Tenant resources cordoning, when enable resources cannot be deleted. + Cordoned bool `json:"cordoned,omitempty"` + // Prevent accidental deletion of the Tenant. + // When enabled, the deletion request will be declined. + PreventDeletion bool `json:"preventDeletion,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,shortName=tnt +// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant" +// +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceOptions.quota",description="The max amount of Namespaces can be created" +// +kubebuilder:printcolumn:name="Namespace count",type="integer",JSONPath=".status.size",description="The total amount of Namespaces in use" +// +kubebuilder:printcolumn:name="Node selector",type="string",JSONPath=".spec.nodeSelector",description="Node Selector applied to Pods" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age" + +// Tenant is the Schema for the tenants API. +type Tenant struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TenantSpec `json:"spec,omitempty"` + Status TenantStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// TenantList contains a list of Tenant. +type TenantList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Tenant `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Tenant{}, &TenantList{}) +} diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go new file mode 100644 index 00000000..71908929 --- /dev/null +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -0,0 +1,672 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta2 + +import ( + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec. +func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec { + if in == nil { + return nil + } + out := new(AdditionalMetadataSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) { + *out = *in + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]v1.Subject, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec. +func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { + if in == nil { + return nil + } + out := new(AdditionalRoleBindingsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) { + *out = *in + if in.Exact != nil { + in, out := &in.Exact, &out.Exact + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec. +func (in *AllowedListSpec) DeepCopy() *AllowedListSpec { + if in == nil { + return nil + } + out := new(AllowedListSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllowedServices) DeepCopyInto(out *AllowedServices) { + *out = *in + if in.NodePort != nil { + in, out := &in.NodePort, &out.NodePort + *out = new(bool) + **out = **in + } + if in.ExternalName != nil { + in, out := &in.ExternalName, &out.ExternalName + *out = new(bool) + **out = **in + } + if in.LoadBalancer != nil { + in, out := &in.LoadBalancer, &out.LoadBalancer + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedServices. +func (in *AllowedServices) DeepCopy() *AllowedServices { + if in == nil { + return nil + } + out := new(AllowedServices) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in ByKindAndName) DeepCopyInto(out *ByKindAndName) { + { + in := &in + *out = make(ByKindAndName, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ByKindAndName. +func (in ByKindAndName) DeepCopy() ByKindAndName { + if in == nil { + return nil + } + out := new(ByKindAndName) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CapsuleConfiguration) DeepCopyInto(out *CapsuleConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfiguration. +func (in *CapsuleConfiguration) DeepCopy() *CapsuleConfiguration { + if in == nil { + return nil + } + out := new(CapsuleConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CapsuleConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CapsuleConfigurationList) DeepCopyInto(out *CapsuleConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CapsuleConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfigurationList. +func (in *CapsuleConfigurationList) DeepCopy() *CapsuleConfigurationList { + if in == nil { + return nil + } + out := new(CapsuleConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CapsuleConfigurationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CapsuleConfigurationSpec) DeepCopyInto(out *CapsuleConfigurationSpec) { + *out = *in + if in.UserGroups != nil { + in, out := &in.UserGroups, &out.UserGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.CapsuleResources = in.CapsuleResources + if in.NodeMetadata != nil { + in, out := &in.NodeMetadata, &out.NodeMetadata + *out = new(NodeMetadata) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfigurationSpec. +func (in *CapsuleConfigurationSpec) DeepCopy() *CapsuleConfigurationSpec { + if in == nil { + return nil + } + out := new(CapsuleConfigurationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CapsuleResources) DeepCopyInto(out *CapsuleResources) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleResources. +func (in *CapsuleResources) DeepCopy() *CapsuleResources { + if in == nil { + return nil + } + out := new(CapsuleResources) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { + *out = *in + if in.Allowed != nil { + in, out := &in.Allowed, &out.Allowed + *out = make([]AllowedIP, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec. +func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec { + if in == nil { + return nil + } + out := new(ExternalServiceIPsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ForbiddenListSpec) DeepCopyInto(out *ForbiddenListSpec) { + *out = *in + if in.Exact != nil { + in, out := &in.Exact, &out.Exact + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForbiddenListSpec. +func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec { + if in == nil { + return nil + } + out := new(ForbiddenListSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IngressOptions) DeepCopyInto(out *IngressOptions) { + *out = *in + if in.AllowedClasses != nil { + in, out := &in.AllowedClasses, &out.AllowedClasses + *out = new(AllowedListSpec) + (*in).DeepCopyInto(*out) + } + if in.AllowedHostnames != nil { + in, out := &in.AllowedHostnames, &out.AllowedHostnames + *out = new(AllowedListSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressOptions. +func (in *IngressOptions) DeepCopy() *IngressOptions { + if in == nil { + return nil + } + out := new(IngressOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]corev1.LimitRangeSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangesSpec. +func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec { + if in == nil { + return nil + } + out := new(LimitRangesSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) { + *out = *in + if in.Quota != nil { + in, out := &in.Quota, &out.Quota + *out = new(int32) + **out = **in + } + if in.AdditionalMetadata != nil { + in, out := &in.AdditionalMetadata, &out.AdditionalMetadata + *out = new(AdditionalMetadataSpec) + (*in).DeepCopyInto(*out) + } + in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels) + in.ForbiddenAnnotations.DeepCopyInto(&out.ForbiddenAnnotations) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceOptions. +func (in *NamespaceOptions) DeepCopy() *NamespaceOptions { + if in == nil { + return nil + } + out := new(NamespaceOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]networkingv1.NetworkPolicySpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec. +func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { + if in == nil { + return nil + } + out := new(NetworkPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeMetadata) DeepCopyInto(out *NodeMetadata) { + *out = *in + in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels) + in.ForbiddenAnnotations.DeepCopyInto(&out.ForbiddenAnnotations) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeMetadata. +func (in *NodeMetadata) DeepCopy() *NodeMetadata { + if in == nil { + return nil + } + out := new(NodeMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NonLimitedResourceError) DeepCopyInto(out *NonLimitedResourceError) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NonLimitedResourceError. +func (in *NonLimitedResourceError) DeepCopy() *NonLimitedResourceError { + if in == nil { + return nil + } + out := new(NonLimitedResourceError) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in OwnerListSpec) DeepCopyInto(out *OwnerListSpec) { + { + in := &in + *out = make(OwnerListSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerListSpec. +func (in OwnerListSpec) DeepCopy() OwnerListSpec { + if in == nil { + return nil + } + out := new(OwnerListSpec) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) { + *out = *in + if in.ClusterRoles != nil { + in, out := &in.ClusterRoles, &out.ClusterRoles + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ProxyOperations != nil { + in, out := &in.ProxyOperations, &out.ProxyOperations + *out = make([]ProxySettings, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerSpec. +func (in *OwnerSpec) DeepCopy() *OwnerSpec { + if in == nil { + return nil + } + out := new(OwnerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxySettings) DeepCopyInto(out *ProxySettings) { + *out = *in + if in.Operations != nil { + in, out := &in.Operations, &out.Operations + *out = make([]ProxyOperation, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxySettings. +func (in *ProxySettings) DeepCopy() *ProxySettings { + if in == nil { + return nil + } + out := new(ProxySettings) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]corev1.ResourceQuotaSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec. +func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { + if in == nil { + return nil + } + out := new(ResourceQuotaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) { + *out = *in + if in.AdditionalMetadata != nil { + in, out := &in.AdditionalMetadata, &out.AdditionalMetadata + *out = new(AdditionalMetadataSpec) + (*in).DeepCopyInto(*out) + } + if in.AllowedServices != nil { + in, out := &in.AllowedServices, &out.AllowedServices + *out = new(AllowedServices) + (*in).DeepCopyInto(*out) + } + if in.ExternalServiceIPs != nil { + in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs + *out = new(ExternalServiceIPsSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions. +func (in *ServiceOptions) DeepCopy() *ServiceOptions { + if in == nil { + return nil + } + out := new(ServiceOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Tenant) DeepCopyInto(out *Tenant) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tenant. +func (in *Tenant) DeepCopy() *Tenant { + if in == nil { + return nil + } + out := new(Tenant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Tenant) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantList) DeepCopyInto(out *TenantList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Tenant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantList. +func (in *TenantList) DeepCopy() *TenantList { + if in == nil { + return nil + } + out := new(TenantList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TenantList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { + *out = *in + if in.Owners != nil { + in, out := &in.Owners, &out.Owners + *out = make(OwnerListSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NamespaceOptions != nil { + in, out := &in.NamespaceOptions, &out.NamespaceOptions + *out = new(NamespaceOptions) + (*in).DeepCopyInto(*out) + } + if in.ServiceOptions != nil { + in, out := &in.ServiceOptions, &out.ServiceOptions + *out = new(ServiceOptions) + (*in).DeepCopyInto(*out) + } + if in.StorageClasses != nil { + in, out := &in.StorageClasses, &out.StorageClasses + *out = new(AllowedListSpec) + (*in).DeepCopyInto(*out) + } + in.IngressOptions.DeepCopyInto(&out.IngressOptions) + if in.ContainerRegistries != nil { + in, out := &in.ContainerRegistries, &out.ContainerRegistries + *out = new(AllowedListSpec) + (*in).DeepCopyInto(*out) + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.NetworkPolicies.DeepCopyInto(&out.NetworkPolicies) + in.LimitRanges.DeepCopyInto(&out.LimitRanges) + in.ResourceQuota.DeepCopyInto(&out.ResourceQuota) + if in.AdditionalRoleBindings != nil { + in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings + *out = make([]AdditionalRoleBindingsSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ImagePullPolicies != nil { + in, out := &in.ImagePullPolicies, &out.ImagePullPolicies + *out = make([]ImagePullPolicySpec, len(*in)) + copy(*out, *in) + } + if in.PriorityClasses != nil { + in, out := &in.PriorityClasses, &out.PriorityClasses + *out = new(AllowedListSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec. +func (in *TenantSpec) DeepCopy() *TenantSpec { + if in == nil { + return nil + } + out := new(TenantSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantStatus) DeepCopyInto(out *TenantStatus) { + *out = *in + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantStatus. +func (in *TenantStatus) DeepCopy() *TenantStatus { + if in == nil { + return nil + } + out := new(TenantStatus) + in.DeepCopyInto(out) + return out +} diff --git a/main.go b/main.go index 1bba13fe..7cb64a83 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ import ( capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" configcontroller "github.com/clastix/capsule/controllers/config" rbaccontroller "github.com/clastix/capsule/controllers/rbac" servicelabelscontroller "github.com/clastix/capsule/controllers/servicelabels" @@ -57,6 +58,7 @@ func init() { utilruntime.Must(capsulev1alpha1.AddToScheme(scheme)) utilruntime.Must(capsulev1beta1.AddToScheme(scheme)) + utilruntime.Must(capsulev1beta2.AddToScheme(scheme)) utilruntime.Must(apiextensionsv1.AddToScheme(scheme)) } From a3391d68af1071f86eee795b57d71402d1aa94b8 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 20 Sep 2022 18:36:19 +0200 Subject: [PATCH 013/153] chore(kustomize): introducing v1beta2 api group --- .../crds/capsuleconfiguration-crd.yaml | 104 +- ...sule.clastix.io_capsuleconfigurations.yaml | 140 +- .../crd/bases/capsule.clastix.io_tenants.yaml | 2168 +++++++++++++++-- config/crd/kustomization.yaml | 1 + .../webhook_in_capsuleconfiguration.yaml | 18 + config/crd/patches/webhook_in_tenants.yaml | 1 + config/install.yaml | 858 ++++++- config/webhook/manifests.yaml | 2 - 8 files changed, 3054 insertions(+), 238 deletions(-) create mode 100644 config/crd/patches/webhook_in_capsuleconfiguration.yaml diff --git a/charts/capsule/crds/capsuleconfiguration-crd.yaml b/charts/capsule/crds/capsuleconfiguration-crd.yaml index 7665e040..40cdab7d 100644 --- a/charts/capsule/crds/capsuleconfiguration-crd.yaml +++ b/charts/capsule/crds/capsuleconfiguration-crd.yaml @@ -3,9 +3,20 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.5.0 - creationTimestamp: null name: capsuleconfigurations.capsule.clastix.io spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: capsule-webhook-service + namespace: capsule-system + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1beta1 + - v1beta2 group: capsule.clastix.io names: kind: CapsuleConfiguration @@ -48,9 +59,98 @@ spec: type: object served: true storage: true + - name: v1beta2 + schema: + openAPIV3Schema: + description: CapsuleConfiguration is the Schema for the Capsule configuration API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CapsuleConfigurationSpec defines the Capsule configuration. + properties: + enableTLSReconciler: + default: true + description: Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager. + type: boolean + forceTenantPrefix: + default: false + description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. + type: boolean + nodeMetadata: + description: Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes. + properties: + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot set for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + overrides: + description: Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations. + properties: + TLSSecretName: + default: capsule-tls + description: Defines the Secret name used for the webhook server. Must be in the same Namespace where the Capsule Deployment is deployed. + type: string + mutatingWebhookConfigurationName: + default: capsule-mutating-webhook-configuration + description: Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + type: string + validatingWebhookConfigurationName: + default: capsule-validating-webhook-configuration + description: Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + type: string + required: + - TLSSecretName + - mutatingWebhookConfigurationName + - validatingWebhookConfigurationName + type: object + protectedNamespaceRegex: + description: Disallow creation of namespaces, whose name matches this regexp + type: string + userGroups: + default: + - capsule.clastix.io + description: Names of the groups for Capsule users. + items: + type: string + type: array + required: + - enableTLSReconciler + - nodeMetadata + - overrides + type: object + type: object + served: true + storage: false status: acceptedNames: kind: "" plural: "" conditions: [] - storedVersions: [] \ No newline at end of file + storedVersions: [] diff --git a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml index 5ce5bdfe..aae170bf 100644 --- a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml +++ b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml @@ -1,10 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: capsuleconfigurations.capsule.clastix.io spec: @@ -19,13 +18,18 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: CapsuleConfiguration is the Schema for the Capsule configuration API. + description: CapsuleConfiguration is the Schema for the Capsule configuration + API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -34,10 +38,14 @@ spec: properties: forceTenantPrefix: default: false - description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. + description: Enforces the Tenant owner, during Namespace creation, + to name it using the selected Tenant name as prefix, separated by + a dash. This is useful to avoid Namespace name collision in a public + CaaS environment. type: boolean protectedNamespaceRegex: - description: Disallow creation of namespaces, whose name matches this regexp + description: Disallow creation of namespaces, whose name matches this + regexp type: string userGroups: default: @@ -50,9 +58,115 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + - name: v1beta2 + schema: + openAPIV3Schema: + description: CapsuleConfiguration is the Schema for the Capsule configuration + API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CapsuleConfigurationSpec defines the Capsule configuration. + properties: + enableTLSReconciler: + default: true + description: Toggles the TLS reconciler, the controller that is able + to generate CA and certificates for the webhooks when not using + an already provided CA and certificate, or when these are managed + externally with Vault, or cert-manager. + type: boolean + forceTenantPrefix: + default: false + description: Enforces the Tenant owner, during Namespace creation, + to name it using the selected Tenant name as prefix, separated by + a dash. This is useful to avoid Namespace name collision in a public + CaaS environment. + type: boolean + nodeMetadata: + description: Allows to set the forbidden metadata for the worker nodes + that could be patched by a Tenant. This applies only if the Tenant + has an active NodeSelector, and the Owner have right to patch their + nodes. + properties: + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot + set for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set + for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + overrides: + description: Allows to set different name rather than the canonical + one for the Capsule configuration objects, such as webhook secret + or configurations. + properties: + TLSSecretName: + default: capsule-tls + description: Defines the Secret name used for the webhook server. + Must be in the same Namespace where the Capsule Deployment is + deployed. + type: string + mutatingWebhookConfigurationName: + default: capsule-mutating-webhook-configuration + description: Name of the MutatingWebhookConfiguration which contains + the dynamic admission controller paths and resources. + type: string + validatingWebhookConfigurationName: + default: capsule-validating-webhook-configuration + description: Name of the ValidatingWebhookConfiguration which + contains the dynamic admission controller paths and resources. + type: string + required: + - TLSSecretName + - mutatingWebhookConfigurationName + - validatingWebhookConfigurationName + type: object + protectedNamespaceRegex: + description: Disallow creation of namespaces, whose name matches this + regexp + type: string + userGroups: + default: + - capsule.clastix.io + description: Names of the groups for Capsule users. + items: + type: string + type: array + required: + - enableTLSReconciler + - nodeMetadata + - overrides + type: object + type: object + served: true + storage: false diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index 0298fb28..474977cf 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -1,10 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 + controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null name: tenants.capsule.clastix.io spec: @@ -49,10 +48,14 @@ spec: description: Tenant is the Schema for the tenants API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -67,24 +70,37 @@ spec: subjects: description: kubebuilder:validation:Minimum=1 items: - description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. properties: apiGroup: - description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. type: string kind: - description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. type: string name: description: Name of the object being referenced. type: string namespace: - description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. type: string required: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -130,12 +146,15 @@ spec: type: object limitRanges: items: - description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + description: LimitRangeSpec defines a min/max usage limit for resources + that match on kind. properties: limits: - description: Limits is the list of LimitRangeItem objects that are enforced. + description: Limits is the list of LimitRangeItem objects that + are enforced. items: - description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. properties: default: additionalProperties: @@ -144,7 +163,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Default resource requirement limit value by resource name if resource limit is omitted. + description: Default resource requirement limit value + by resource name if resource limit is omitted. type: object defaultRequest: additionalProperties: @@ -153,7 +173,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + description: DefaultRequest is the default resource requirement + request value by resource name if resource request is + omitted. type: object max: additionalProperties: @@ -162,7 +184,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource name. + description: Max usage constraints on this kind by resource + name. type: object maxLimitRequestRatio: additionalProperties: @@ -171,7 +194,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + description: MaxLimitRequestRatio if specified, the named + resource must have a request and limit that are both + non-zero where limit divided by request is less than + or equal to the enumerated value; this represents the + max burst for the named resource. type: object min: additionalProperties: @@ -180,10 +207,12 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource name. + description: Min usage constraints on this kind by resource + name. type: object type: - description: Type of resource that this limit applies to. + description: Type of resource that this limit applies + to. type: string required: - type @@ -213,44 +242,93 @@ spec: description: NetworkPolicySpec provides the specification of a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows the + traffic), OR if the traffic matches at least one egress rule + across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure that + the pods it selects are isolated by default). This field is + beta-level in 1.8 items: - description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by a + NetworkPolicySpec's podSelector. The traffic must match + both ports and to. This type is beta-level in 1.8 properties: ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of destination ports for outgoing traffic. + Each item in this list is combined using a logical OR. + If this field is empty or missing, this rule matches + all ports (traffic not restricted by port). If this + field is present and contains at least one item, then + this rule allows traffic only if the traffic matches + at least one port in the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port to allow + traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range of + ports from port to endPort, inclusive, should + be allowed by the policy. This field cannot be + defined if the port field is not defined or if + the port field is defined as a named (string) + port. The endPort must be equal or greater than + port. This feature is in Beta state and is enabled + by default. It can be disabled using the Feature + Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. This + can either be a numerical or named port on a pod. + If this field is not provided, this matches all + port names and numbers. If present, only traffic + on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) which + traffic must match. If not specified, this field + defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list are + combined using a logical OR operation. If this field + is empty or missing, this rule matches all destinations + (traffic not restricted by destination). If this field + is present and contains at least one item, this rule + allows traffic only if the traffic matches at least + one item in the to list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer to allow + traffic to/from. Only certain combinations of fields + are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither of + the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing the + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs that + should not be included within an IP Block + Valid examples are "192.168.1.1/24" or "2001:db9::/64" + Except values will be rejected if they are + outside the CIDR range items: type: string type: array @@ -258,21 +336,43 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label selector + semantics; if present but empty, it selects all + namespaces. \n If PodSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects all + Pods in the Namespaces selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -284,25 +384,54 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which selects + Pods. This field follows standard label selector + semantics; if present but empty, it selects all + pods. \n If NamespaceSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects the + Pods matching PodSelector in the policy's own + Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -314,31 +443,65 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object type: array ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + description: List of ingress rules to be applied to the selected + pods. Traffic is allowed to a pod if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows the + traffic), OR if the traffic source is the pod's local node, + OR if the traffic matches at least one ingress rule across + all of the NetworkPolicy objects whose podSelector matches + the pod. If this field is empty then this NetworkPolicy does + not allow any traffic (and serves solely to ensure that the + pods it selects are isolated by default) items: - description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by a + NetworkPolicySpec's podSelector. The traffic must match + both ports and from. properties: from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + description: List of sources which should be able to access + the pods selected for this rule. Items in this list + are combined using a logical OR operation. If this field + is empty or missing, this rule matches all sources (traffic + not restricted by source). If this field is present + and contains at least one item, this rule allows traffic + only if the traffic matches at least one item in the + from list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer to allow + traffic to/from. Only certain combinations of fields + are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither of + the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing the + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs that + should not be included within an IP Block + Valid examples are "192.168.1.1/24" or "2001:db9::/64" + Except values will be rejected if they are + outside the CIDR range items: type: string type: array @@ -346,21 +509,43 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label selector + semantics; if present but empty, it selects all + namespaces. \n If PodSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects all + Pods in the Namespaces selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -372,25 +557,54 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which selects + Pods. This field follows standard label selector + semantics; if present but empty, it selects all + pods. \n If NamespaceSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects the + Pods matching PodSelector in the policy's own + Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -402,50 +616,94 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in this + list is combined using a logical OR. If this field is + empty or missing, this rule matches all ports (traffic + not restricted by port). If this field is present and + contains at least one item, then this rule allows traffic + only if the traffic matches at least one port in the + list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port to allow + traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range of + ports from port to endPort, inclusive, should + be allowed by the policy. This field cannot be + defined if the port field is not defined or if + the port field is defined as a named (string) + port. The endPort must be equal or greater than + port. This feature is in Beta state and is enabled + by default. It can be disabled using the Feature + Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. This + can either be a numerical or named port on a pod. + If this field is not provided, this matches all + port names and numbers. If present, only traffic + on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) which + traffic must match. If not specified, this field + defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + description: Selects the pods to which this NetworkPolicy object + applies. The array of ingress rules is applied to any pods + selected by this field. Multiple network policies can select + the same set of pods. In this case, the ingress rules for + each are combined additively. This field is NOT optional and + follows standard label selector semantics. An empty podSelector + matches all pods in this namespace. properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector + applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. This + array is replaced during a strategic merge patch. items: type: string type: array @@ -457,13 +715,31 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress section) + are assumed to affect Ingress. If you want to write an egress-only + policy, you must explicitly specify policyTypes [ "Egress" + ]. Likewise, if you want to write a policy that specifies + that no egress is allowed, you must specify a policyTypes + value that include "Egress" (since such a policy would not + include an Egress section and would otherwise default to just + [ "Ingress" ]). This field is beta-level in 1.8 items: - description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 type: string type: array required: @@ -490,7 +766,8 @@ spec: type: object resourceQuotas: items: - description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + description: ResourceQuotaSpec defines the desired hard limits to + enforce for Quota. properties: hard: additionalProperties: @@ -499,24 +776,39 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + description: 'hard is the set of desired hard limits for each + named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' type: object scopeSelector: - description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + description: scopeSelector is also a collection of filters like + scopes that must match each object tracked by a quota but + expressed using ScopeSelectorOperator in combination with + possible values. For a resource to match, both scopes AND + scopeSelector (if specified in spec), must be matched. properties: matchExpressions: - description: A list of scope selector requirements by scope of the resources. + description: A list of scope selector requirements by scope + of the resources. items: - description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + description: A scoped-resource selector requirement is + a selector that contains values, a scope name, and an + operator that relates the scope name and values. properties: operator: - description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + description: Represents a scope's relationship to + a set of values. Valid operators are In, NotIn, + Exists, DoesNotExist. type: string scopeName: - description: The name of the scope that the selector applies to. + description: The name of the scope that the selector + applies to. type: string values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: An array of string values. If the operator + is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during + a strategic merge patch. items: type: string type: array @@ -526,10 +818,14 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: - description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + description: A collection of filters that must match each object + tracked by a quota. If not specified, the quota matches all + objects. items: - description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + description: A ResourceQuotaScope defines a filter that must + match each object tracked by a quota type: string type: array type: object @@ -601,10 +897,14 @@ spec: description: Tenant is the Schema for the tenants API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -612,7 +912,9 @@ spec: description: TenantSpec defines the desired state of Tenant. properties: additionalRoleBindings: - description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. + description: Specifies additional RoleBindings assigned to the Tenant. + Capsule will ensure that all namespaces in the Tenant always contain + the RoleBinding for the given ClusterRole. Optional. items: properties: clusterRoleName: @@ -620,24 +922,37 @@ spec: subjects: description: kubebuilder:validation:Minimum=1 items: - description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. properties: apiGroup: - description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. type: string kind: - description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. type: string name: description: Name of the object being referenced. type: string namespace: - description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. type: string required: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -645,7 +960,9 @@ spec: type: object type: array containerRegistries: - description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + description: Specifies the trusted Image Registries assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed trusted registries. Optional. properties: allowed: items: @@ -655,7 +972,9 @@ spec: type: string type: object imagePullPolicies: - description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. + description: Specify the allowed values for the imagePullPolicies + option in Pod resources. Capsule assures that all Pod resources + created in the Tenant can use only one of the allowed policy. Optional. items: enum: - Always @@ -664,10 +983,14 @@ spec: type: string type: array ingressOptions: - description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + description: Specifies options for the Ingress resources, such as + allowed hostnames and IngressClass. Optional. properties: allowedClasses: - description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + description: Specifies the allowed IngressClasses assigned to + the Tenant. Capsule assures that all Ingress resources created + in the Tenant can use only one of the allowed IngressClasses. + Optional. properties: allowed: items: @@ -677,7 +1000,10 @@ spec: type: string type: object allowedHostnames: - description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + description: Specifies the allowed hostnames in Ingresses for + the given Tenant. Capsule assures that all Ingress resources + created in the Tenant can use only one of the allowed hostnames. + Optional. properties: allowed: items: @@ -688,7 +1014,15 @@ spec: type: object hostnameCollisionScope: default: Disabled - description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + description: "Defines the scope of hostname collision check performed + when Tenant Owners create Ingress with allowed hostnames. \n + - Cluster: disallow the creation of an Ingress if the pair hostname + and path is already used across the Namespaces managed by Capsule. + \n - Tenant: disallow the creation of an Ingress if the pair + hostname and path is already used across the Namespaces of the + Tenant. \n - Namespace: disallow the creation of an Ingress + if the pair hostname and path is already used in the Ingress + Namespace. \n Optional." enum: - Cluster - Tenant @@ -697,16 +1031,21 @@ spec: type: string type: object limitRanges: - description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + description: Specifies the resource min/max usage restrictions to + the Tenant. The assigned values are inherited by any namespace created + in the Tenant. Optional. properties: items: items: - description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + description: LimitRangeSpec defines a min/max usage limit for + resources that match on kind. properties: limits: - description: Limits is the list of LimitRangeItem objects that are enforced. + description: Limits is the list of LimitRangeItem objects + that are enforced. items: - description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. properties: default: additionalProperties: @@ -715,7 +1054,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Default resource requirement limit value by resource name if resource limit is omitted. + description: Default resource requirement limit value + by resource name if resource limit is omitted. type: object defaultRequest: additionalProperties: @@ -724,7 +1064,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + description: DefaultRequest is the default resource + requirement request value by resource name if resource + request is omitted. type: object max: additionalProperties: @@ -733,7 +1075,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource name. + description: Max usage constraints on this kind by + resource name. type: object maxLimitRequestRatio: additionalProperties: @@ -742,7 +1085,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + description: MaxLimitRequestRatio if specified, the + named resource must have a request and limit that + are both non-zero where limit divided by request + is less than or equal to the enumerated value; this + represents the max burst for the named resource. type: object min: additionalProperties: @@ -751,10 +1098,12 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource name. + description: Min usage constraints on this kind by + resource name. type: object type: - description: Type of resource that this limit applies to. + description: Type of resource that this limit applies + to. type: string required: - type @@ -766,10 +1115,14 @@ spec: type: array type: object namespaceOptions: - description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + description: Specifies options for the Namespaces, such as additional + metadata or maximum number of namespaces allowed for that Tenant. + Once the namespace quota assigned to the Tenant has been reached, + the Tenant owner cannot create further namespaces. Optional. properties: additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + description: Specifies additional labels and annotations the Capsule + operator places on any Namespace resource in the Tenant. Optional. properties: annotations: additionalProperties: @@ -781,57 +1134,116 @@ spec: type: object type: object quota: - description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + description: Specifies the maximum number of namespaces allowed + for that Tenant. Once the namespace quota assigned to the Tenant + has been reached, the Tenant owner cannot create further namespaces. + Optional. format: int32 minimum: 1 type: integer type: object networkPolicies: - description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + description: Specifies the NetworkPolicies assigned to the Tenant. + The assigned NetworkPolicies are inherited by any namespace created + in the Tenant. Optional. properties: items: items: - description: NetworkPolicySpec provides the specification of a NetworkPolicy + description: NetworkPolicySpec provides the specification of + a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows + the traffic), OR if the traffic matches at least one egress + rule across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure + that the pods it selects are isolated by default). This + field is beta-level in 1.8 items: - description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and to. This type is beta-level in + 1.8 properties: ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of destination ports for outgoing + traffic. Each item in this list is combined using + a logical OR. If this field is empty or missing, + this rule matches all ports (traffic not restricted + by port). If this field is present and contains + at least one item, then this rule allows traffic + only if the traffic matches at least one port in + the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port + to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list + are combined using a logical OR operation. If this + field is empty or missing, this rule matches all + destinations (traffic not restricted by destination). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the to list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range items: type: string type: array @@ -839,21 +1251,45 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -865,25 +1301,55 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -895,31 +1361,67 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object type: array ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + description: List of ingress rules to be applied to the + selected pods. Traffic is allowed to a pod if there are + no NetworkPolicies selecting the pod (and cluster policy + otherwise allows the traffic), OR if the traffic source + is the pod's local node, OR if the traffic matches at + least one ingress rule across all of the NetworkPolicy + objects whose podSelector matches the pod. If this field + is empty then this NetworkPolicy does not allow any traffic + (and serves solely to ensure that the pods it selects + are isolated by default) items: - description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and from. properties: from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + description: List of sources which should be able + to access the pods selected for this rule. Items + in this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all sources (traffic not restricted by source). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the from list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range items: type: string type: array @@ -927,21 +1429,45 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -953,25 +1479,55 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -983,50 +1539,96 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in + this list is combined using a logical OR. If this + field is empty or missing, this rule matches all + ports (traffic not restricted by port). If this + field is present and contains at least one item, + then this rule allows traffic only if the traffic + matches at least one port in the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port + to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + description: Selects the pods to which this NetworkPolicy + object applies. The array of ingress rules is applied + to any pods selected by this field. Multiple network policies + can select the same set of pods. In this case, the ingress + rules for each are combined additively. This field is + NOT optional and follows standard label selector semantics. + An empty podSelector matches all pods in this namespace. properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector + applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. items: type: string type: array @@ -1038,13 +1640,32 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress + section) are assumed to affect Ingress. If you want to + write an egress-only policy, you must explicitly specify + policyTypes [ "Egress" ]. Likewise, if you want to write + a policy that specifies that no egress is allowed, you + must specify a policyTypes value that include "Egress" + (since such a policy would not include an Egress section + and would otherwise default to just [ "Ingress" ]). This + field is beta-level in 1.8 items: - description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 type: string type: array required: @@ -1055,14 +1676,19 @@ spec: nodeSelector: additionalProperties: type: string - description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + description: Specifies the label to control the placement of pods + on a given pool of worker nodes. All namespaces created within the + Tenant will have the node selector annotation. This annotation tells + the Kubernetes scheduler to place pods on the nodes having the selector + label. Optional. type: object owners: description: Specifies the owners of the Tenant. Mandatory. items: properties: kind: - description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + description: Kind of tenant owner. Possible values are "User", + "Group", and "ServiceAccount" enum: - User - Group @@ -1101,7 +1727,9 @@ spec: type: object type: array priorityClasses: - description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + description: Specifies the allowed priorityClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed PriorityClasses. Optional. properties: allowed: items: @@ -1111,11 +1739,17 @@ spec: type: string type: object resourceQuotas: - description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + description: Specifies a list of ResourceQuota resources assigned + to the Tenant. The assigned values are inherited by any namespace + created in the Tenant. The Capsule operator aggregates ResourceQuota + at Tenant level, so that the hard quota is never crossed for the + given Tenant. This permits the Tenant owner to consume resources + in the Tenant regardless of the namespace. Optional. properties: items: items: - description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + description: ResourceQuotaSpec defines the desired hard limits + to enforce for Quota. properties: hard: additionalProperties: @@ -1124,24 +1758,40 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + description: 'hard is the set of desired hard limits for + each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' type: object scopeSelector: - description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + description: scopeSelector is also a collection of filters + like scopes that must match each object tracked by a quota + but expressed using ScopeSelectorOperator in combination + with possible values. For a resource to match, both scopes + AND scopeSelector (if specified in spec), must be matched. properties: matchExpressions: - description: A list of scope selector requirements by scope of the resources. + description: A list of scope selector requirements by + scope of the resources. items: - description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + description: A scoped-resource selector requirement + is a selector that contains values, a scope name, + and an operator that relates the scope name and + values. properties: operator: - description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + description: Represents a scope's relationship + to a set of values. Valid operators are In, + NotIn, Exists, DoesNotExist. type: string scopeName: - description: The name of the scope that the selector applies to. + description: The name of the scope that the selector + applies to. type: string values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is + replaced during a strategic merge patch. items: type: string type: array @@ -1151,27 +1801,35 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: - description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + description: A collection of filters that must match each + object tracked by a quota. If not specified, the quota + matches all objects. items: - description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + description: A ResourceQuotaScope defines a filter that + must match each object tracked by a quota type: string type: array type: object type: array scope: default: Tenant - description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + description: Define if the Resource Budget should compute resource + across all Namespaces in the Tenant or individually per cluster. + Default is Tenant enum: - Tenant - Namespace type: string type: object serviceOptions: - description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + description: Specifies options for the Service, such as additional + metadata or block of certain type of Services. Optional. properties: additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + description: Specifies additional labels and annotations the Capsule + operator places on any Service resource in the Tenant. Optional. properties: annotations: additionalProperties: @@ -1187,19 +1845,24 @@ spec: properties: externalName: default: true - description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if ExternalName service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean loadBalancer: default: true - description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if LoadBalancer service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean nodePort: default: true - description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if NodePort service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean type: object externalIPs: - description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + description: Specifies the external IPs that can be used in Services + with type ClusterIP. An empty list means no IPs are allowed. + Optional. properties: allowed: items: @@ -1211,7 +1874,10 @@ spec: type: object type: object storageClasses: - description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + description: Specifies the allowed StorageClasses assigned to the + Tenant. Capsule assures that all PersistentVolumeClaim resources + created in the Tenant can use only one of the allowed StorageClasses. + Optional. properties: allowed: items: @@ -1236,7 +1902,8 @@ spec: type: integer state: default: Active - description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + description: The operational state of the Tenant. Possible values + are "Active", "Cordoned". enum: - Cordoned - Active @@ -1250,9 +1917,1098 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + - additionalPrinterColumns: + - description: The actual state of the Tenant + jsonPath: .status.state + name: State + type: string + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceOptions.quota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. + Capsule will ensure that all namespaces in the Tenant always contain + the RoleBinding for the given ClusterRole. Optional. + items: + properties: + clusterRoleName: + type: string + subjects: + description: kubebuilder:validation:Minimum=1 + items: + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + required: + - clusterRoleName + - subjects + type: object + type: array + containerRegistries: + description: Specifies the trusted Image Registries assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed trusted registries. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + cordoned: + description: Toggling the Tenant resources cordoning, when enable + resources cannot be deleted. + type: boolean + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies + option in Pod resources. Capsule assures that all Pod resources + created in the Tenant can use only one of the allowed policy. Optional. + items: + enum: + - Always + - Never + - IfNotPresent + type: string + type: array + ingressOptions: + description: Specifies options for the Ingress resources, such as + allowed hostnames and IngressClass. Optional. + properties: + allowWildcardHostnames: + description: Toggles the ability for Ingress resources created + in a Tenant to have a hostname wildcard. + type: boolean + allowedClasses: + description: Specifies the allowed IngressClasses assigned to + the Tenant. Capsule assures that all Ingress resources created + in the Tenant can use only one of the allowed IngressClasses. + Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for + the given Tenant. Capsule assures that all Ingress resources + created in the Tenant can use only one of the allowed hostnames. + Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed + when Tenant Owners create Ingress with allowed hostnames. \n + - Cluster: disallow the creation of an Ingress if the pair hostname + and path is already used across the Namespaces managed by Capsule. + \n - Tenant: disallow the creation of an Ingress if the pair + hostname and path is already used across the Namespaces of the + Tenant. \n - Namespace: disallow the creation of an Ingress + if the pair hostname and path is already used in the Ingress + Namespace. \n Optional." + enum: + - Cluster + - Tenant + - Namespace + - Disabled + type: string + required: + - allowWildcardHostnames + type: object + limitRanges: + description: Specifies the resource min/max usage restrictions to + the Tenant. The assigned values are inherited by any namespace created + in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for + resources that match on kind. + properties: + limits: + description: Limits is the list of LimitRangeItem objects + that are enforced. + items: + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value + by resource name if resource limit is omitted. + type: object + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource + requirement request value by resource name if resource + request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by + resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the + named resource must have a request and limit that + are both non-zero where limit divided by request + is less than or equal to the enumerated value; this + represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by + resource name. + type: object + type: + description: Type of resource that this limit applies + to. + type: string + required: + - type + type: object + type: array + required: + - limits + type: object + type: array + type: object + namespaceOptions: + description: Specifies options for the Namespaces, such as additional + metadata or maximum number of namespaces allowed for that Tenant. + Once the namespace quota assigned to the Tenant has been reached, + the Tenant owner cannot create further namespaces. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule + operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot + set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set + for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + quota: + description: Specifies the maximum number of namespaces allowed + for that Tenant. Once the namespace quota assigned to the Tenant + has been reached, the Tenant owner cannot create further namespaces. + Optional. + format: int32 + minimum: 1 + type: integer + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. + The assigned NetworkPolicies are inherited by any namespace created + in the Tenant. Optional. + properties: + items: + items: + description: NetworkPolicySpec provides the specification of + a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows + the traffic), OR if the traffic matches at least one egress + rule across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure + that the pods it selects are isolated by default). This + field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and to. This type is beta-level in + 1.8 + properties: + ports: + description: List of destination ports for outgoing + traffic. Each item in this list is combined using + a logical OR. If this field is empty or missing, + this rule matches all ports (traffic not restricted + by port). If this field is present and contains + at least one item, then this rule allows traffic + only if the traffic matches at least one port in + the list. + items: + description: NetworkPolicyPort describes a port + to allow traffic on + properties: + endPort: + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. + type: string + type: object + type: array + to: + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list + are combined using a logical OR operation. If this + field is empty or missing, this rule matches all + destinations (traffic not restricted by destination). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. + properties: + cidr: + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the + selected pods. Traffic is allowed to a pod if there are + no NetworkPolicies selecting the pod (and cluster policy + otherwise allows the traffic), OR if the traffic source + is the pod's local node, OR if the traffic matches at + least one ingress rule across all of the NetworkPolicy + objects whose podSelector matches the pod. If this field + is empty then this NetworkPolicy does not allow any traffic + (and serves solely to ensure that the pods it selects + are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and from. + properties: + from: + description: List of sources which should be able + to access the pods selected for this rule. Items + in this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all sources (traffic not restricted by source). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. + properties: + cidr: + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in + this list is combined using a logical OR. If this + field is empty or missing, this rule matches all + ports (traffic not restricted by port). If this + field is present and contains at least one item, + then this rule allows traffic only if the traffic + matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port + to allow traffic on + properties: + endPort: + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. + type: string + type: object + type: array + type: object + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy + object applies. The array of ingress rules is applied + to any pods selected by this field. Multiple network policies + can select the same set of pods. In this case, the ingress + rules for each are combined additively. This field is + NOT optional and follows standard label selector semantics. + An empty podSelector matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress + section) are assumed to affect Ingress. If you want to + write an egress-only policy, you must explicitly specify + policyTypes [ "Egress" ]. Likewise, if you want to write + a policy that specifies that no egress is allowed, you + must specify a policyTypes value that include "Egress" + (since such a policy would not include an Egress section + and would otherwise default to just [ "Ingress" ]). This + field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector + type: object + type: array + type: object + nodeSelector: + additionalProperties: + type: string + description: Specifies the label to control the placement of pods + on a given pool of worker nodes. All namespaces created within the + Tenant will have the node selector annotation. This annotation tells + the Kubernetes scheduler to place pods on the nodes having the selector + label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. + items: + properties: + clusterRoles: + default: + - admin + - capsule-namespace-deleter + description: Defines additional cluster-roles for the specific + Owner. + items: + type: string + type: array + kind: + description: Kind of tenant owner. Possible values are "User", + "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of tenant owner. + type: string + proxySettings: + description: Proxy settings for tenant owner. + items: + properties: + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses + type: string + operations: + items: + enum: + - List + - Update + - Delete + type: string + type: array + required: + - kind + - operations + type: object + type: array + required: + - kind + - name + type: object + type: array + preventDeletion: + description: Prevent accidental deletion of the Tenant. When enabled, + the deletion request will be declined. + type: boolean + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed PriorityClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned + to the Tenant. The assigned values are inherited by any namespace + created in the Tenant. The Capsule operator aggregates ResourceQuota + at Tenant level, so that the hard quota is never crossed for the + given Tenant. This permits the Tenant owner to consume resources + in the Tenant regardless of the namespace. Optional. + properties: + items: + items: + description: ResourceQuotaSpec defines the desired hard limits + to enforce for Quota. + properties: + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for + each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters + like scopes that must match each object tracked by a quota + but expressed using ScopeSelectorOperator in combination + with possible values. For a resource to match, both scopes + AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by + scope of the resources. + items: + description: A scoped-resource selector requirement + is a selector that contains values, a scope name, + and an operator that relates the scope name and + values. + properties: + operator: + description: Represents a scope's relationship + to a set of values. Valid operators are In, + NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector + applies to. + type: string + values: + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each + object tracked by a quota. If not specified, the quota + matches all objects. + items: + description: A ResourceQuotaScope defines a filter that + must match each object tracked by a quota + type: string + type: array + type: object + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource + across all Namespaces in the Tenant or individually per cluster. + Default is Tenant + enum: + - Tenant + - Namespace + type: string + type: object + serviceOptions: + description: Specifies options for the Service, such as additional + metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule + operator places on any Service resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + allowedServices: + description: Block or deny certain type of Services. Optional. + properties: + externalName: + default: true + description: Specifies if ExternalName service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services + with type ClusterIP. An empty list means no IPs are allowed. + Optional. + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string + type: array + required: + - allowed + type: object + type: object + storageClasses: + description: Specifies the allowed StorageClasses assigned to the + Tenant. Capsule assures that all PersistentVolumeClaim resources + created in the Tenant can use only one of the allowed StorageClasses. + Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + required: + - owners + type: object + status: + description: Returns the observed state of the Tenant. + properties: + namespaces: + description: List of namespaces assigned to the Tenant. + items: + type: string + type: array + size: + description: How many namespaces are assigned to the Tenant. + type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values + are "Active", "Cordoned". + enum: + - Cordoned + - Active + type: string + required: + - size + - state + type: object + type: object + served: true + storage: false + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 1f37ca41..4c753b21 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -12,3 +12,4 @@ configurations: patchesStrategicMerge: - patches/webhook_in_tenants.yaml +- patches/webhook_in_capsuleconfiguration.yaml diff --git a/config/crd/patches/webhook_in_capsuleconfiguration.yaml b/config/crd/patches/webhook_in_capsuleconfiguration.yaml new file mode 100644 index 00000000..a23cd9e4 --- /dev/null +++ b/config/crd/patches/webhook_in_capsuleconfiguration.yaml @@ -0,0 +1,18 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: capsuleconfigurations.capsule.clastix.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1beta1 + - v1beta2 diff --git a/config/crd/patches/webhook_in_tenants.yaml b/config/crd/patches/webhook_in_tenants.yaml index 342c408c..bc8182da 100644 --- a/config/crd/patches/webhook_in_tenants.yaml +++ b/config/crd/patches/webhook_in_tenants.yaml @@ -15,3 +15,4 @@ spec: conversionReviewVersions: - v1alpha1 - v1beta1 + - v1beta2 diff --git a/config/install.yaml b/config/install.yaml index 36f80d93..cef1ef3c 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -9,10 +9,21 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.10.0 name: capsuleconfigurations.capsule.clastix.io spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: capsule-webhook-service + namespace: capsule-system + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1beta1 + - v1beta2 group: capsule.clastix.io names: kind: CapsuleConfiguration @@ -55,18 +66,101 @@ spec: type: object served: true storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + - name: v1beta2 + schema: + openAPIV3Schema: + description: CapsuleConfiguration is the Schema for the Capsule configuration API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CapsuleConfigurationSpec defines the Capsule configuration. + properties: + enableTLSReconciler: + default: true + description: Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager. + type: boolean + forceTenantPrefix: + default: false + description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment. + type: boolean + nodeMetadata: + description: Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes. + properties: + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot set for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set for their nodes. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + overrides: + description: Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations. + properties: + TLSSecretName: + default: capsule-tls + description: Defines the Secret name used for the webhook server. Must be in the same Namespace where the Capsule Deployment is deployed. + type: string + mutatingWebhookConfigurationName: + default: capsule-mutating-webhook-configuration + description: Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + type: string + validatingWebhookConfigurationName: + default: capsule-validating-webhook-configuration + description: Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources. + type: string + required: + - TLSSecretName + - mutatingWebhookConfigurationName + - validatingWebhookConfigurationName + type: object + protectedNamespaceRegex: + description: Disallow creation of namespaces, whose name matches this regexp + type: string + userGroups: + default: + - capsule.clastix.io + description: Names of the groups for Capsule users. + items: + type: string + type: array + required: + - enableTLSReconciler + - nodeMetadata + - overrides + type: object + type: object + served: true + storage: false --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 + controller-gen.kubebuilder.io/version: v0.10.0 name: tenants.capsule.clastix.io spec: conversion: @@ -80,6 +174,7 @@ spec: conversionReviewVersions: - v1alpha1 - v1beta1 + - v1beta2 group: capsule.clastix.io names: kind: Tenant @@ -157,6 +252,7 @@ spec: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -359,6 +455,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -389,6 +486,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object @@ -447,6 +545,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -477,6 +576,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: @@ -532,6 +632,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: @@ -598,6 +699,7 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. items: @@ -710,6 +812,7 @@ spec: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -940,6 +1043,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -970,6 +1074,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object @@ -1028,6 +1133,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -1058,6 +1164,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: @@ -1113,6 +1220,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: @@ -1223,6 +1331,7 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. items: @@ -1322,12 +1431,731 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + - additionalPrinterColumns: + - description: The actual state of the Tenant + jsonPath: .status.state + name: State + type: string + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceOptions.quota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. + items: + properties: + clusterRoleName: + type: string + subjects: + description: kubebuilder:validation:Minimum=1 + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + required: + - clusterRoleName + - subjects + type: object + type: array + containerRegistries: + description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + cordoned: + description: Toggling the Tenant resources cordoning, when enable resources cannot be deleted. + type: boolean + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. + items: + enum: + - Always + - Never + - IfNotPresent + type: string + type: array + ingressOptions: + description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + properties: + allowWildcardHostnames: + description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. + type: boolean + allowedClasses: + description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + enum: + - Cluster + - Tenant + - Namespace + - Disabled + type: string + required: + - allowWildcardHostnames + type: object + limitRanges: + description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + properties: + limits: + description: Limits is the list of LimitRangeItem objects that are enforced. + items: + description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value by resource name if resource limit is omitted. + type: object + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by resource name. + type: object + type: + description: Type of resource that this limit applies to. + type: string + required: + - type + type: object + type: array + required: + - limits + type: object + type: array + type: object + namespaceOptions: + description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + quota: + description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + format: int32 + minimum: 1 + type: integer + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: NetworkPolicySpec provides the specification of a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + properties: + ports: + description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + to: + description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + properties: + from: + description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + type: object + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector + type: object + type: array + type: object + nodeSelector: + additionalProperties: + type: string + description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. + items: + properties: + clusterRoles: + default: + - admin + - capsule-namespace-deleter + description: Defines additional cluster-roles for the specific Owner. + items: + type: string + type: array + kind: + description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of tenant owner. + type: string + proxySettings: + description: Proxy settings for tenant owner. + items: + properties: + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses + type: string + operations: + items: + enum: + - List + - Update + - Delete + type: string + type: array + required: + - kind + - operations + type: object + type: array + required: + - kind + - name + type: object + type: array + preventDeletion: + description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined. + type: boolean + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + properties: + items: + items: + description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + properties: + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by scope of the resources. + items: + description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + properties: + operator: + description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector applies to. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + items: + description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + type: string + type: array + type: object + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + enum: + - Tenant + - Namespace + type: string + type: object + serviceOptions: + description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + allowedServices: + description: Block or deny certain type of Services. Optional. + properties: + externalName: + default: true + description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string + type: array + required: + - allowed + type: object + type: object + storageClasses: + description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + required: + - owners + type: object + status: + description: Returns the observed state of the Tenant. + properties: + namespaces: + description: List of namespaces assigned to the Tenant. + items: + type: string + type: array + size: + description: How many namespaces are assigned to the Tenant. + type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + enum: + - Cordoned + - Active + type: string + required: + - size + - state + type: object + type: object + served: true + storage: false + subresources: + status: {} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index f76c3196..768a76f0 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -1,4 +1,3 @@ - --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration @@ -26,7 +25,6 @@ webhooks: resources: - namespaces sideEffects: None - --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration From 85aa53df6f5db5a82c7eeed05b7c16004436eed0 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 20 Sep 2022 18:36:30 +0200 Subject: [PATCH 014/153] chore(helm)!: introducing v1beta2 api group --- .../crds/capsuleconfiguration-crd.yaml | 10 +- charts/capsule/crds/tenant-crd.yaml | 752 +++++++++++++++++- 2 files changed, 746 insertions(+), 16 deletions(-) diff --git a/charts/capsule/crds/capsuleconfiguration-crd.yaml b/charts/capsule/crds/capsuleconfiguration-crd.yaml index 40cdab7d..d20fb126 100644 --- a/charts/capsule/crds/capsuleconfiguration-crd.yaml +++ b/charts/capsule/crds/capsuleconfiguration-crd.yaml @@ -1,8 +1,10 @@ + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 + controller-gen.kubebuilder.io/version: v0.10.0 name: capsuleconfigurations.capsule.clastix.io spec: conversion: @@ -148,9 +150,3 @@ spec: type: object served: true storage: false -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index 538006a9..f171bd4b 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -1,9 +1,10 @@ + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.5.0 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.10.0 name: tenants.capsule.clastix.io spec: conversion: @@ -14,10 +15,10 @@ spec: name: capsule-webhook-service namespace: capsule-system path: /convert - port: 443 conversionReviewVersions: - v1alpha1 - v1beta1 + - v1beta2 group: capsule.clastix.io names: kind: Tenant @@ -95,6 +96,7 @@ spec: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -297,6 +299,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -327,6 +330,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object @@ -385,6 +389,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -415,6 +420,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: @@ -470,6 +476,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: @@ -536,6 +543,7 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. items: @@ -648,6 +656,7 @@ spec: - kind - name type: object + x-kubernetes-map-type: atomic type: array required: - clusterRoleName @@ -878,6 +887,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -908,6 +918,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array type: object @@ -966,6 +977,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic podSelector: description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." properties: @@ -996,6 +1008,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic type: object type: array ports: @@ -1051,6 +1064,7 @@ spec: description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object + x-kubernetes-map-type: atomic policyTypes: description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: @@ -1161,6 +1175,7 @@ spec: type: object type: array type: object + x-kubernetes-map-type: atomic scopes: description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. items: @@ -1260,9 +1275,728 @@ spec: storage: true subresources: status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] + - additionalPrinterColumns: + - description: The actual state of the Tenant + jsonPath: .status.state + name: State + type: string + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceOptions.quota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. + items: + properties: + clusterRoleName: + type: string + subjects: + description: kubebuilder:validation:Minimum=1 + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + required: + - clusterRoleName + - subjects + type: object + type: array + containerRegistries: + description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + cordoned: + description: Toggling the Tenant resources cordoning, when enable resources cannot be deleted. + type: boolean + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. + items: + enum: + - Always + - Never + - IfNotPresent + type: string + type: array + ingressOptions: + description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + properties: + allowWildcardHostnames: + description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. + type: boolean + allowedClasses: + description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + enum: + - Cluster + - Tenant + - Namespace + - Disabled + type: string + required: + - allowWildcardHostnames + type: object + limitRanges: + description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + properties: + limits: + description: Limits is the list of LimitRangeItem objects that are enforced. + items: + description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value by resource name if resource limit is omitted. + type: object + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by resource name. + type: object + type: + description: Type of resource that this limit applies to. + type: string + required: + - type + type: object + type: array + required: + - limits + type: object + type: array + type: object + namespaceOptions: + description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + quota: + description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + format: int32 + minimum: 1 + type: integer + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: NetworkPolicySpec provides the specification of a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + properties: + ports: + description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + to: + description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + properties: + from: + description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + type: object + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector + type: object + type: array + type: object + nodeSelector: + additionalProperties: + type: string + description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. + items: + properties: + clusterRoles: + default: + - admin + - capsule-namespace-deleter + description: Defines additional cluster-roles for the specific Owner. + items: + type: string + type: array + kind: + description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of tenant owner. + type: string + proxySettings: + description: Proxy settings for tenant owner. + items: + properties: + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses + type: string + operations: + items: + enum: + - List + - Update + - Delete + type: string + type: array + required: + - kind + - operations + type: object + type: array + required: + - kind + - name + type: object + type: array + preventDeletion: + description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined. + type: boolean + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + properties: + items: + items: + description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + properties: + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by scope of the resources. + items: + description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + properties: + operator: + description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector applies to. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + items: + description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + type: string + type: array + type: object + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + enum: + - Tenant + - Namespace + type: string + type: object + serviceOptions: + description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + allowedServices: + description: Block or deny certain type of Services. Optional. + properties: + externalName: + default: true + description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string + type: array + required: + - allowed + type: object + type: object + storageClasses: + description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + required: + - owners + type: object + status: + description: Returns the observed state of the Tenant. + properties: + namespaces: + description: List of namespaces assigned to the Tenant. + items: + type: string + type: array + size: + description: How many namespaces are assigned to the Tenant. + type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + enum: + - Cordoned + - Active + type: string + required: + - size + - state + type: object + type: object + served: true + storage: false + subresources: + status: {} From d20e466732c19db1ac2231fdc847455f078f5d1f Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 20 Sep 2022 18:37:02 +0200 Subject: [PATCH 015/153] feat: support for ca update on crds objects --- controllers/tls/manager.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/controllers/tls/manager.go b/controllers/tls/manager.go index d67e3a61..c2cafa25 100644 --- a/controllers/tls/manager.go +++ b/controllers/tls/manager.go @@ -132,7 +132,10 @@ func (r Reconciler) ReconcileCertificates(ctx context.Context, certSecret *corev return r.updateValidatingWebhookConfiguration(ctx, caBundle) }) group.Go(func() error { - return r.updateCustomResourceDefinition(ctx, caBundle) + return r.updateTenantCustomResourceDefinition(ctx, "tenants.capsule.clastix.io", caBundle) + }) + group.Go(func() error { + return r.updateTenantCustomResourceDefinition(ctx, "capsuleconfigurations.capsule.clastix.io", caBundle) }) operatorPods, err := r.getOperatorPods(ctx) @@ -214,10 +217,10 @@ func (r Reconciler) shouldUpdateCertificate(secret *corev1.Secret) bool { // By default helm doesn't allow to use templates in CRD (https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#method-1-let-helm-do-it-for-you). // In order to overcome this, we are setting conversion strategy in helm chart to None, and then update it with CA and namespace information. -func (r *Reconciler) updateCustomResourceDefinition(ctx context.Context, caBundle []byte) error { +func (r *Reconciler) updateTenantCustomResourceDefinition(ctx context.Context, name string, caBundle []byte) error { return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { crd := &apiextensionsv1.CustomResourceDefinition{} - err = r.Get(ctx, types.NamespacedName{Name: "tenants.capsule.clastix.io"}, crd) + err = r.Get(ctx, types.NamespacedName{Name: name}, crd) if err != nil { r.Log.Error(err, "cannot retrieve CustomResourceDefinition") From f5c2932137f0db9df37f9b75eb2abdf09aa45386 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 20 Sep 2022 18:37:45 +0200 Subject: [PATCH 016/153] docs: introducing v1beta2 api group --- docs/content/general/tenant-crd.md | 2174 +++++++++++++++++++++++++++- 1 file changed, 2170 insertions(+), 4 deletions(-) diff --git a/docs/content/general/tenant-crd.md b/docs/content/general/tenant-crd.md index 13d8cb3f..4a34dc2e 100644 --- a/docs/content/general/tenant-crd.md +++ b/docs/content/general/tenant-crd.md @@ -20,6 +20,7 @@ Packages: - [capsule.clastix.io/v1alpha1](#capsuleclastixiov1alpha1) +- [capsule.clastix.io/v1beta2](#capsuleclastixiov1beta2) - [capsule.clastix.io/v1beta1](#capsuleclastixiov1beta1) # capsule.clastix.io/v1alpha1 @@ -638,14 +639,14 @@ LimitRangeItem defines a min/max usage limit for any resource that matches on ki - additionalAnnotations + annotations map[string]string
false - additionalLabels + labels map[string]string
@@ -1497,14 +1498,14 @@ A scoped-resource selector requirement is a selector that contains values, a sco - additionalAnnotations + annotations map[string]string
false - additionalLabels + labels map[string]string
@@ -1579,6 +1580,2171 @@ TenantStatus defines the observed state of Tenant. +# capsule.clastix.io/v1beta2 + +Resource Types: + +- [CapsuleConfiguration](#capsuleconfiguration) + +- [Tenant](#tenant) + + + + +## CapsuleConfiguration + + + + + + +CapsuleConfiguration is the Schema for the Capsule configuration API. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringcapsule.clastix.io/v1beta2true
kindstringCapsuleConfigurationtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + CapsuleConfigurationSpec defines the Capsule configuration.
+
false
+ + +### CapsuleConfiguration.spec + + + +CapsuleConfigurationSpec defines the Capsule configuration. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
enableTLSReconcilerboolean + Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager.
+
+ Default: true
+
true
nodeMetadataobject + Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes.
+
true
overridesobject + Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations.
+
true
forceTenantPrefixboolean + Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment.
+
+ Default: false
+
false
protectedNamespaceRegexstring + Disallow creation of namespaces, whose name matches this regexp
+
false
userGroups[]string + Names of the groups for Capsule users.
+
+ Default: [capsule.clastix.io]
+
false
+ + +### CapsuleConfiguration.spec.nodeMetadata + + + +Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
forbiddenAnnotationsobject + Define the annotations that a Tenant Owner cannot set for their nodes.
+
true
forbiddenLabelsobject + Define the labels that a Tenant Owner cannot set for their nodes.
+
true
+ + +### CapsuleConfiguration.spec.nodeMetadata.forbiddenAnnotations + + + +Define the annotations that a Tenant Owner cannot set for their nodes. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
denied[]string +
+
false
deniedRegexstring +
+
false
+ + +### CapsuleConfiguration.spec.nodeMetadata.forbiddenLabels + + + +Define the labels that a Tenant Owner cannot set for their nodes. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
denied[]string +
+
false
deniedRegexstring +
+
false
+ + +### CapsuleConfiguration.spec.overrides + + + +Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
TLSSecretNamestring + Defines the Secret name used for the webhook server. Must be in the same Namespace where the Capsule Deployment is deployed.
+
+ Default: capsule-tls
+
true
mutatingWebhookConfigurationNamestring + Name of the MutatingWebhookConfiguration which contains the dynamic admission controller paths and resources.
+
+ Default: capsule-mutating-webhook-configuration
+
true
validatingWebhookConfigurationNamestring + Name of the ValidatingWebhookConfiguration which contains the dynamic admission controller paths and resources.
+
+ Default: capsule-validating-webhook-configuration
+
true
+ +## Tenant + + + + + + +Tenant is the Schema for the tenants API. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringcapsule.clastix.io/v1beta2true
kindstringTenanttrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + TenantSpec defines the desired state of Tenant.
+
false
statusobject + Returns the observed state of the Tenant.
+
false
+ + +### Tenant.spec + + + +TenantSpec defines the desired state of Tenant. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
owners[]object + Specifies the owners of the Tenant. Mandatory.
+
true
additionalRoleBindings[]object + Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
+
false
containerRegistriesobject + Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional.
+
false
cordonedboolean + Toggling the Tenant resources cordoning, when enable resources cannot be deleted.
+
false
imagePullPolicies[]enum + Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
+
false
ingressOptionsobject + Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+
false
limitRangesobject + Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
+
false
namespaceOptionsobject + Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
+
false
networkPoliciesobject + Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
+
false
nodeSelectormap[string]string + Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
+
false
preventDeletionboolean + Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
+
false
priorityClassesobject + Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional.
+
false
resourceQuotasobject + Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
+
false
serviceOptionsobject + Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+
false
storageClassesobject + Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
+
false
+ + +### Tenant.spec.owners[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
clusterRoles[]string + Defines additional cluster-roles for the specific Owner.
+
true
kindenum + Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
+
+ Enum: User, Group, ServiceAccount
+
true
namestring + Name of tenant owner.
+
true
proxySettings[]object + Proxy settings for tenant owner.
+
false
+ + +### Tenant.spec.owners[index].proxySettings[index] + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindenum +
+
+ Enum: Nodes, StorageClasses, IngressClasses, PriorityClasses
+
true
operations[]enum +
+
true
+ + +### Tenant.spec.additionalRoleBindings[index] + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
clusterRoleNamestring +
+
true
subjects[]object + kubebuilder:validation:Minimum=1
+
true
+ + +### Tenant.spec.additionalRoleBindings[index].subjects[index] + + + +Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindstring + Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error.
+
true
namestring + Name of the object being referenced.
+
true
apiGroupstring + APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects.
+
false
namespacestring + Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error.
+
false
+ + +### Tenant.spec.containerRegistries + + + +Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
+ + +### Tenant.spec.ingressOptions + + + +Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowWildcardHostnamesboolean + Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard.
+
true
allowedClassesobject + Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
+
false
allowedHostnamesobject + Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional.
+
false
hostnameCollisionScopeenum + Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. + - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. + - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. + - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. + Optional.
+
+ Enum: Cluster, Tenant, Namespace, Disabled
+ Default: Disabled
+
false
+ + +### Tenant.spec.ingressOptions.allowedClasses + + + +Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
+ + +### Tenant.spec.ingressOptions.allowedHostnames + + + +Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
+ + +### Tenant.spec.limitRanges + + + +Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
items[]object +
+
false
+ + +### Tenant.spec.limitRanges.items[index] + + + +LimitRangeSpec defines a min/max usage limit for resources that match on kind. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
limits[]object + Limits is the list of LimitRangeItem objects that are enforced.
+
true
+ + +### Tenant.spec.limitRanges.items[index].limits[index] + + + +LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
typestring + Type of resource that this limit applies to.
+
true
defaultmap[string]int or string + Default resource requirement limit value by resource name if resource limit is omitted.
+
false
defaultRequestmap[string]int or string + DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
+
false
maxmap[string]int or string + Max usage constraints on this kind by resource name.
+
false
maxLimitRequestRatiomap[string]int or string + MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.
+
false
minmap[string]int or string + Min usage constraints on this kind by resource name.
+
false
+ + +### Tenant.spec.namespaceOptions + + + +Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
forbiddenAnnotationsobject + Define the annotations that a Tenant Owner cannot set for their Namespace resources.
+
true
forbiddenLabelsobject + Define the labels that a Tenant Owner cannot set for their Namespace resources.
+
true
additionalMetadataobject + Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
+
false
quotainteger + Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
+
+ Format: int32
+ Minimum: 1
+
false
+ + +### Tenant.spec.namespaceOptions.forbiddenAnnotations + + + +Define the annotations that a Tenant Owner cannot set for their Namespace resources. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
denied[]string +
+
false
deniedRegexstring +
+
false
+ + +### Tenant.spec.namespaceOptions.forbiddenLabels + + + +Define the labels that a Tenant Owner cannot set for their Namespace resources. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
denied[]string +
+
false
deniedRegexstring +
+
false
+ + +### Tenant.spec.namespaceOptions.additionalMetadata + + + +Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
annotationsmap[string]string +
+
false
labelsmap[string]string +
+
false
+ + +### Tenant.spec.networkPolicies + + + +Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
items[]object +
+
false
+ + +### Tenant.spec.networkPolicies.items[index] + + + +NetworkPolicySpec provides the specification of a NetworkPolicy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
podSelectorobject + Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
+
true
egress[]object + List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
+
false
ingress[]object + List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
+
false
policyTypes[]string + List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
+
false
+ + +### Tenant.spec.networkPolicies.items[index].podSelector + + + +Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].podSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index] + + + +NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
ports[]object + List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
+
false
to[]object + List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].ports[index] + + + +NetworkPolicyPort describes a port to allow traffic on + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
endPortinteger + If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
+
+ Format: int32
+
false
portint or string + The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
+
false
protocolstring + The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+
+ Default: TCP
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index] + + + +NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
ipBlockobject + IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+
false
namespaceSelectorobject + Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
+
false
podSelectorobject + This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index].ipBlock + + + +IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
cidrstring + CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+
true
except[]string + Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index].namespaceSelector + + + +Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index].namespaceSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index].podSelector + + + +This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].egress[index].to[index].podSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index] + + + +NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
from[]object + List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
+
false
ports[]object + List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index] + + + +NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
ipBlockobject + IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+
false
namespaceSelectorobject + Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
+
false
podSelectorobject + This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].ipBlock + + + +IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
cidrstring + CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+
true
except[]string + Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].namespaceSelector + + + +Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].namespaceSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].podSelector + + + +This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].from[index].podSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.networkPolicies.items[index].ingress[index].ports[index] + + + +NetworkPolicyPort describes a port to allow traffic on + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
endPortinteger + If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
+
+ Format: int32
+
false
portint or string + The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
+
false
protocolstring + The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+
+ Default: TCP
+
false
+ + +### Tenant.spec.priorityClasses + + + +Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
+ + +### Tenant.spec.resourceQuotas + + + +Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
items[]object +
+
false
scopeenum + Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant
+
+ Enum: Tenant, Namespace
+ Default: Tenant
+
false
+ + +### Tenant.spec.resourceQuotas.items[index] + + + +ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
hardmap[string]int or string + hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/
+
false
scopeSelectorobject + scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.
+
false
scopes[]string + A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
+
false
+ + +### Tenant.spec.resourceQuotas.items[index].scopeSelector + + + +scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + A list of scope selector requirements by scope of the resources.
+
false
+ + +### Tenant.spec.resourceQuotas.items[index].scopeSelector.matchExpressions[index] + + + +A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
operatorstring + Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
+
true
scopeNamestring + The name of the scope that the selector applies to.
+
true
values[]string + An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### Tenant.spec.serviceOptions + + + +Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
additionalMetadataobject + Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
+
false
allowedServicesobject + Block or deny certain type of Services. Optional.
+
false
externalIPsobject + Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
+
false
+ + +### Tenant.spec.serviceOptions.additionalMetadata + + + +Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
annotationsmap[string]string +
+
false
labelsmap[string]string +
+
false
+ + +### Tenant.spec.serviceOptions.allowedServices + + + +Block or deny certain type of Services. Optional. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
externalNameboolean + Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+
false
loadBalancerboolean + Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+
false
nodePortboolean + Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
+
+ Default: true
+
false
+ + +### Tenant.spec.serviceOptions.externalIPs + + + +Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
true
+ + +### Tenant.spec.storageClasses + + + +Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
+ + +### Tenant.status + + + +Returns the observed state of the Tenant. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
sizeinteger + How many namespaces are assigned to the Tenant.
+
true
stateenum + The operational state of the Tenant. Possible values are "Active", "Cordoned".
+
+ Enum: Cordoned, Active
+ Default: Active
+
true
namespaces[]string + List of namespaces assigned to the Tenant.
+
false
+ # capsule.clastix.io/v1beta1 Resource Types: From cf5292487061a10fc583f4557bff1e3d42078f0f Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 27 Sep 2022 18:27:47 +0200 Subject: [PATCH 017/153] refactor: abstracting types used by several api versions --- api/v1alpha1/additional_metadata.go | 6 +- api/v1alpha1/allowed_list.go | 37 -- api/v1alpha1/allowed_list_test.go | 73 ---- api/v1alpha1/conversion_hub.go | 262 +++++------- api/v1alpha1/conversion_hub_test.go | 43 +- api/v1alpha1/tenant_func.go | 16 +- api/v1alpha1/tenant_types.go | 18 +- api/v1alpha1/tenant_webhook.go | 8 +- api/v1alpha1/zz_generated.deepcopy.go | 100 +---- api/v1beta1/additional_role_bindings.go | 12 - api/v1beta1/allowed_list.go | 38 -- api/v1beta1/deny_wildcard.go | 4 +- api/v1beta1/forbidden_list.go | 38 -- api/v1beta1/forbidden_list_test.go | 73 ---- api/v1beta1/hostname_collision_scope.go | 14 - api/v1beta1/image_pull_policy.go | 11 - api/v1beta1/ingress_options.go | 10 +- api/v1beta1/limit_ranges.go | 10 - api/v1beta1/namespace_options.go | 40 +- api/v1beta1/owner_list.go | 28 +- api/v1beta1/owner_role.go | 4 +- api/v1beta1/service_allowed_ips.go | 11 - api/v1beta1/service_options.go | 10 +- api/v1beta1/tenant_annotations.go | 6 - api/v1beta1/tenant_func.go | 20 +- api/v1beta1/tenant_labels.go | 32 -- api/v1beta1/tenant_types.go | 22 +- api/v1beta1/zz_generated.deepcopy.go | 205 +-------- api/v1beta2/allowed_list_test.go | 73 ---- ...=> capsuleconfiguration_convertion_hub.go} | 19 +- api/v1beta2/capsuleconfiguration_types.go | 6 +- api/v1beta2/conversion_hub.go | 401 ------------------ api/v1beta2/ingress_options.go | 10 +- api/v1beta2/namespace_options.go | 10 +- api/v1beta2/network_policy.go | 12 - api/v1beta2/resource_quota.go | 21 - api/v1beta2/service_allowed_ips.go | 11 - api/v1beta2/tenant_annotations.go | 26 -- api/v1beta2/tenant_conversion_hub.go | 246 +++++++++++ api/v1beta2/tenant_types.go | 20 +- api/v1beta2/zz_generated.deepcopy.go | 207 +-------- charts/capsule/crds/tenant-crd.yaml | 8 +- controllers/servicelabels/abstract.go | 3 +- .../tenant/annotations.go | 14 +- controllers/tenant/limitranges.go | 5 +- controllers/tenant/namespaces.go | 14 +- controllers/tenant/networkpolicies.go | 5 +- controllers/tenant/resourcequotas.go | 14 +- controllers/tenant/rolebindings.go | 16 +- controllers/tenant/utils.go | 2 +- e2e/additional_role_bindings_test.go | 3 +- e2e/allowed_external_ips_test.go | 7 +- e2e/container_registry_test.go | 3 +- e2e/disable_externalname_test.go | 5 +- e2e/disable_loadbalancer_test.go | 5 +- e2e/disable_node_ports_test.go | 5 +- e2e/enable_loadbalancer_test.go | 5 +- e2e/imagepullpolicy_multiple_test.go | 3 +- e2e/imagepullpolicy_single_test.go | 3 +- e2e/ingress_class_extensions_test.go | 3 +- e2e/ingress_class_networking_test.go | 3 +- ..._hostnames_collision_cluster_scope_test.go | 5 +- ...gress_hostnames_collision_disabled_test.go | 3 +- ...ostnames_collision_namespace_scope_test.go | 3 +- ...s_hostnames_collision_tenant_scope_test.go | 3 +- e2e/ingress_hostnames_test.go | 3 +- e2e/namespace_additional_metadata_test.go | 3 +- e2e/owner_webhooks_test.go | 9 +- e2e/pod_priority_class_test.go | 3 +- e2e/resource_quota_exceeded_test.go | 5 +- e2e/selecting_non_owned_tenant_test.go | 3 +- e2e/selecting_tenant_with_label_test.go | 3 +- e2e/service_metadata_test.go | 5 +- e2e/storage_class_test.go | 3 +- e2e/tenant_resources_changes_test.go | 7 +- e2e/tenant_resources_test.go | 7 +- .../api}/additional_metadata.go | 4 +- .../api}/additional_role_bindings.go | 4 +- {api/v1beta2 => pkg/api}/allowed_list.go | 4 +- {api/v1beta1 => pkg/api}/allowed_list_test.go | 3 +- .../api}/external_service_ips.go | 4 +- {api/v1beta2 => pkg/api}/forbidden_list.go | 6 +- .../api}/forbidden_list_test.go | 3 +- .../api}/hostname_collision_scope.go | 2 +- {api/v1beta2 => pkg/api}/image_pull_policy.go | 2 +- {api/v1beta2 => pkg/api}/limit_ranges.go | 4 +- {api/v1beta1 => pkg/api}/network_policy.go | 4 +- {api/v1beta1 => pkg/api}/resource_quota.go | 4 +- .../api}/service_allowed_types.go | 4 +- {api/v1beta2 => pkg/api}/service_options.go | 4 +- pkg/api/zz_generated.deepcopy.go | 250 +++++++++++ pkg/configuration/client.go | 2 +- pkg/configuration/configuration.go | 2 +- {api/v1alpha1 => pkg/utils}/tenant_labels.go | 8 +- pkg/webhook/ingress/errors.go | 18 +- pkg/webhook/ingress/validate_collision.go | 13 +- pkg/webhook/namespace/errors.go | 2 +- pkg/webhook/namespace/patch.go | 3 +- pkg/webhook/networkpolicy/validating.go | 3 +- pkg/webhook/node/errors.go | 2 +- pkg/webhook/ownerreference/patching.go | 3 +- pkg/webhook/pod/containerregistry_errors.go | 6 +- pkg/webhook/pod/priorityclass_errors.go | 6 +- pkg/webhook/pvc/errors.go | 12 +- pkg/webhook/service/errors.go | 4 +- 105 files changed, 1007 insertions(+), 1783 deletions(-) delete mode 100644 api/v1alpha1/allowed_list.go delete mode 100644 api/v1alpha1/allowed_list_test.go delete mode 100644 api/v1beta1/additional_role_bindings.go delete mode 100644 api/v1beta1/allowed_list.go delete mode 100644 api/v1beta1/forbidden_list.go delete mode 100644 api/v1beta1/forbidden_list_test.go delete mode 100644 api/v1beta1/hostname_collision_scope.go delete mode 100644 api/v1beta1/image_pull_policy.go delete mode 100644 api/v1beta1/limit_ranges.go delete mode 100644 api/v1beta1/service_allowed_ips.go delete mode 100644 api/v1beta1/tenant_labels.go delete mode 100644 api/v1beta2/allowed_list_test.go rename api/v1beta2/{capsuleconfiguration_funcs.go => capsuleconfiguration_convertion_hub.go} (84%) delete mode 100644 api/v1beta2/conversion_hub.go delete mode 100644 api/v1beta2/network_policy.go delete mode 100644 api/v1beta2/resource_quota.go delete mode 100644 api/v1beta2/service_allowed_ips.go delete mode 100644 api/v1beta2/tenant_annotations.go create mode 100644 api/v1beta2/tenant_conversion_hub.go rename api/v1alpha1/tenant_annotations.go => controllers/tenant/annotations.go (69%) rename {api/v1beta1 => pkg/api}/additional_metadata.go (82%) rename {api/v1alpha1 => pkg/api}/additional_role_bindings.go (85%) rename {api/v1beta2 => pkg/api}/allowed_list.go (93%) rename {api/v1beta1 => pkg/api}/allowed_list_test.go (98%) rename {api/v1alpha1 => pkg/api}/external_service_ips.go (84%) rename {api/v1beta2 => pkg/api}/forbidden_list.go (85%) rename {api/v1beta2 => pkg/api}/forbidden_list_test.go (98%) rename {api/v1beta2 => pkg/api}/hostname_collision_scope.go (96%) rename {api/v1beta2 => pkg/api}/image_pull_policy.go (93%) rename {api/v1beta2 => pkg/api}/limit_ranges.go (80%) rename {api/v1beta1 => pkg/api}/network_policy.go (82%) rename {api/v1beta1 => pkg/api}/resource_quota.go (92%) rename {api/v1beta2 => pkg/api}/service_allowed_types.go (92%) rename {api/v1beta2 => pkg/api}/service_options.go (92%) create mode 100644 pkg/api/zz_generated.deepcopy.go rename {api/v1alpha1 => pkg/utils}/tenant_labels.go (79%) diff --git a/api/v1alpha1/additional_metadata.go b/api/v1alpha1/additional_metadata.go index 0f032997..377688df 100644 --- a/api/v1alpha1/additional_metadata.go +++ b/api/v1alpha1/additional_metadata.go @@ -3,7 +3,7 @@ package v1alpha1 -type AdditionalMetadataSpec struct { - AdditionalLabels map[string]string `json:"additionalLabels,omitempty"` - AdditionalAnnotations map[string]string `json:"additionalAnnotations,omitempty"` +type AdditionalMetadata struct { + Labels map[string]string `json:"additionalLabels,omitempty"` + Annotations map[string]string `json:"additionalAnnotations,omitempty"` } diff --git a/api/v1alpha1/allowed_list.go b/api/v1alpha1/allowed_list.go deleted file mode 100644 index eac46577..00000000 --- a/api/v1alpha1/allowed_list.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1alpha1 - -import ( - "regexp" - "sort" - "strings" -) - -type AllowedListSpec struct { - Exact []string `json:"allowed,omitempty"` - Regex string `json:"allowedRegex,omitempty"` -} - -func (in *AllowedListSpec) ExactMatch(value string) (ok bool) { - if len(in.Exact) > 0 { - sort.SliceStable(in.Exact, func(i, j int) bool { - return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) - }) - - i := sort.SearchStrings(in.Exact, value) - - ok = i < len(in.Exact) && in.Exact[i] == value - } - - return -} - -func (in AllowedListSpec) RegexMatch(value string) (ok bool) { - if len(in.Regex) > 0 { - ok = regexp.MustCompile(in.Regex).MatchString(value) - } - - return -} diff --git a/api/v1alpha1/allowed_list_test.go b/api/v1alpha1/allowed_list_test.go deleted file mode 100644 index 7aef5b60..00000000 --- a/api/v1alpha1/allowed_list_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1alpha1 - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAllowedListSpec_ExactMatch(t *testing.T) { - type tc struct { - In []string - True []string - False []string - } - - for _, tc := range []tc{ - { - []string{"foo", "bar", "bizz", "buzz"}, - []string{"foo", "bar", "bizz", "buzz"}, - []string{"bing", "bong"}, - }, - { - []string{"one", "two", "three"}, - []string{"one", "two", "three"}, - []string{"a", "b", "c"}, - }, - { - nil, - nil, - []string{"any", "value"}, - }, - } { - a := AllowedListSpec{ - Exact: tc.In, - } - - for _, ok := range tc.True { - assert.True(t, a.ExactMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.ExactMatch(ko)) - } - } -} - -func TestAllowedListSpec_RegexMatch(t *testing.T) { - type tc struct { - Regex string - True []string - False []string - } - - for _, tc := range []tc{ - {`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}}, - {``, nil, []string{"any", "value"}}, - } { - a := AllowedListSpec{ - Regex: tc.Regex, - } - - for _, ok := range tc.True { - assert.True(t, a.RegexMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.RegexMatch(ko)) - } - } -} diff --git a/api/v1alpha1/conversion_hub.go b/api/v1alpha1/conversion_hub.go index 00beee62..fe636631 100644 --- a/api/v1alpha1/conversion_hub.go +++ b/api/v1alpha1/conversion_hub.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/conversion" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) const ( @@ -48,7 +49,7 @@ const ( ingressHostnameCollisionScope = "ingress.capsule.clastix.io/hostname-collision-scope" ) -func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { +func (in *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { serviceKindToAnnotationMap := map[capsulev1beta1.ProxyServiceKind][]string{ capsulev1beta1.NodesProxy: {enableNodeListingAnnotation, enableNodeUpdateAnnotation, enableNodeDeletionAnnotation}, capsulev1beta1.StorageClassesProxy: {enableStorageClassListingAnnotation, enableStorageClassUpdateAnnotation, enableStorageClassDeletionAnnotation}, @@ -75,7 +76,7 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { ownerServiceAccountAnnotation: capsulev1beta1.ServiceAccountOwner, } - annotations := t.GetAnnotations() + annotations := in.GetAnnotations() operations := make(map[string]map[capsulev1beta1.ProxyServiceKind][]capsulev1beta1.ProxyOperation) @@ -111,9 +112,9 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { } owners = append(owners, capsulev1beta1.OwnerSpec{ - Kind: capsulev1beta1.OwnerKind(t.Spec.Owner.Kind), - Name: t.Spec.Owner.Name, - ProxyOperations: getProxySettingsForOwner(t.Spec.Owner.Name), + Kind: capsulev1beta1.OwnerKind(in.Spec.Owner.Kind), + Name: in.Spec.Owner.Name, + ProxyOperations: getProxySettingsForOwner(in.Spec.Owner.Name), }) for ownerAnnotation, ownerKind := range annotationToOwnerKindMap { @@ -133,150 +134,134 @@ func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { } // nolint:gocognit,gocyclo,cyclop,maintidx -func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error { +func (in *Tenant) ConvertTo(dstRaw conversion.Hub) error { dst, ok := dstRaw.(*capsulev1beta1.Tenant) if !ok { return fmt.Errorf("expected type *capsulev1beta1.Tenant, got %T", dst) } - annotations := t.GetAnnotations() + annotations := in.GetAnnotations() // ObjectMeta - dst.ObjectMeta = t.ObjectMeta + dst.ObjectMeta = in.ObjectMeta // Spec - if t.Spec.NamespaceQuota != nil { + if in.Spec.NamespaceQuota != nil { if dst.Spec.NamespaceOptions == nil { dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{} } - dst.Spec.NamespaceOptions.Quota = t.Spec.NamespaceQuota + dst.Spec.NamespaceOptions.Quota = in.Spec.NamespaceQuota } - dst.Spec.NodeSelector = t.Spec.NodeSelector + dst.Spec.NodeSelector = in.Spec.NodeSelector - dst.Spec.Owners = t.convertV1Alpha1OwnerToV1Beta1() + dst.Spec.Owners = in.convertV1Alpha1OwnerToV1Beta1() - if t.Spec.NamespacesMetadata != nil { + if in.Spec.NamespacesMetadata != nil { if dst.Spec.NamespaceOptions == nil { dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{} } - dst.Spec.NamespaceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{ - Labels: t.Spec.NamespacesMetadata.AdditionalLabels, - Annotations: t.Spec.NamespacesMetadata.AdditionalAnnotations, + dst.Spec.NamespaceOptions.AdditionalMetadata = &api.AdditionalMetadataSpec{ + Labels: in.Spec.NamespacesMetadata.Labels, + Annotations: in.Spec.NamespacesMetadata.Annotations, } } - if t.Spec.ServicesMetadata != nil { + if in.Spec.ServicesMetadata != nil { if dst.Spec.ServiceOptions == nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{ - AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{ - Labels: t.Spec.ServicesMetadata.AdditionalLabels, - Annotations: t.Spec.ServicesMetadata.AdditionalAnnotations, - }, - } + dst.Spec.ServiceOptions = &api.ServiceOptions{} } - } - if t.Spec.StorageClasses != nil { - dst.Spec.StorageClasses = &capsulev1beta1.AllowedListSpec{ - Exact: t.Spec.StorageClasses.Exact, - Regex: t.Spec.StorageClasses.Regex, + dst.Spec.ServiceOptions.AdditionalMetadata = &api.AdditionalMetadataSpec{ + Labels: in.Spec.ServicesMetadata.Labels, + Annotations: in.Spec.ServicesMetadata.Annotations, } } - if v, annotationOk := t.Annotations[ingressHostnameCollisionScope]; annotationOk { + if in.Spec.StorageClasses != nil { + dst.Spec.StorageClasses = in.Spec.StorageClasses + } + + if v, annotationOk := in.Annotations[ingressHostnameCollisionScope]; annotationOk { switch v { - case string(capsulev1beta1.HostnameCollisionScopeCluster), string(capsulev1beta1.HostnameCollisionScopeTenant), string(capsulev1beta1.HostnameCollisionScopeNamespace): - dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScope(v) + case string(api.HostnameCollisionScopeCluster), string(api.HostnameCollisionScopeTenant), string(api.HostnameCollisionScopeNamespace): + dst.Spec.IngressOptions.HostnameCollisionScope = api.HostnameCollisionScope(v) default: - dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScopeDisabled + dst.Spec.IngressOptions.HostnameCollisionScope = api.HostnameCollisionScopeDisabled } } - if t.Spec.IngressClasses != nil { - dst.Spec.IngressOptions.AllowedClasses = &capsulev1beta1.AllowedListSpec{ - Exact: t.Spec.IngressClasses.Exact, - Regex: t.Spec.IngressClasses.Regex, + if in.Spec.IngressClasses != nil { + dst.Spec.IngressOptions.AllowedClasses = &api.AllowedListSpec{ + Exact: in.Spec.IngressClasses.Exact, + Regex: in.Spec.IngressClasses.Regex, } } - if t.Spec.IngressHostnames != nil { - dst.Spec.IngressOptions.AllowedHostnames = &capsulev1beta1.AllowedListSpec{ - Exact: t.Spec.IngressHostnames.Exact, - Regex: t.Spec.IngressHostnames.Regex, + if in.Spec.IngressHostnames != nil { + dst.Spec.IngressOptions.AllowedHostnames = &api.AllowedListSpec{ + Exact: in.Spec.IngressHostnames.Exact, + Regex: in.Spec.IngressHostnames.Regex, } } - if t.Spec.ContainerRegistries != nil { - dst.Spec.ContainerRegistries = &capsulev1beta1.AllowedListSpec{ - Exact: t.Spec.ContainerRegistries.Exact, - Regex: t.Spec.ContainerRegistries.Regex, + if in.Spec.ContainerRegistries != nil { + dst.Spec.ContainerRegistries = &api.AllowedListSpec{ + Exact: in.Spec.ContainerRegistries.Exact, + Regex: in.Spec.ContainerRegistries.Regex, } } - if len(t.Spec.NetworkPolicies) > 0 { - dst.Spec.NetworkPolicies = capsulev1beta1.NetworkPolicySpec{ - Items: t.Spec.NetworkPolicies, + if len(in.Spec.NetworkPolicies) > 0 { + dst.Spec.NetworkPolicies = api.NetworkPolicySpec{ + Items: in.Spec.NetworkPolicies, } } - if len(t.Spec.LimitRanges) > 0 { - dst.Spec.LimitRanges = capsulev1beta1.LimitRangesSpec{ - Items: t.Spec.LimitRanges, + if len(in.Spec.LimitRanges) > 0 { + dst.Spec.LimitRanges = api.LimitRangesSpec{ + Items: in.Spec.LimitRanges, } } - if len(t.Spec.ResourceQuota) > 0 { - dst.Spec.ResourceQuota = capsulev1beta1.ResourceQuotaSpec{ - Scope: func() capsulev1beta1.ResourceQuotaScope { - if v, annotationOk := t.GetAnnotations()[resourceQuotaScopeAnnotation]; annotationOk { + if len(in.Spec.ResourceQuota) > 0 { + dst.Spec.ResourceQuota = api.ResourceQuotaSpec{ + Scope: func() api.ResourceQuotaScope { + if v, annotationOk := in.GetAnnotations()[resourceQuotaScopeAnnotation]; annotationOk { switch v { - case string(capsulev1beta1.ResourceQuotaScopeNamespace): - return capsulev1beta1.ResourceQuotaScopeNamespace - case string(capsulev1beta1.ResourceQuotaScopeTenant): - return capsulev1beta1.ResourceQuotaScopeTenant + case string(api.ResourceQuotaScopeNamespace): + return api.ResourceQuotaScopeNamespace + case string(api.ResourceQuotaScopeTenant): + return api.ResourceQuotaScopeTenant } } - return capsulev1beta1.ResourceQuotaScopeTenant + return api.ResourceQuotaScopeTenant }(), - Items: t.Spec.ResourceQuota, + Items: in.Spec.ResourceQuota, } } - if len(t.Spec.AdditionalRoleBindings) > 0 { - for _, rb := range t.Spec.AdditionalRoleBindings { - dst.Spec.AdditionalRoleBindings = append(dst.Spec.AdditionalRoleBindings, capsulev1beta1.AdditionalRoleBindingsSpec{ - ClusterRoleName: rb.ClusterRoleName, - Subjects: rb.Subjects, - }) - } - } + dst.Spec.AdditionalRoleBindings = in.Spec.AdditionalRoleBindings - if t.Spec.ExternalServiceIPs != nil { + if in.Spec.ExternalServiceIPs != nil { if dst.Spec.ServiceOptions == nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} - } - - dst.Spec.ServiceOptions.ExternalServiceIPs = &capsulev1beta1.ExternalServiceIPsSpec{ - Allowed: make([]capsulev1beta1.AllowedIP, len(t.Spec.ExternalServiceIPs.Allowed)), + dst.Spec.ServiceOptions = &api.ServiceOptions{} } - for i, IP := range t.Spec.ExternalServiceIPs.Allowed { - dst.Spec.ServiceOptions.ExternalServiceIPs.Allowed[i] = capsulev1beta1.AllowedIP(IP) - } + dst.Spec.ServiceOptions.ExternalServiceIPs = in.Spec.ExternalServiceIPs } pullPolicies, ok := annotations[podAllowedImagePullPolicyAnnotation] if ok { for _, policy := range strings.Split(pullPolicies, ",") { - dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, capsulev1beta1.ImagePullPolicySpec(policy)) + dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, api.ImagePullPolicySpec(policy)) } } - priorityClasses := capsulev1beta1.AllowedListSpec{} + priorityClasses := api.AllowedListSpec{} priorityClassAllowed, ok := annotations[podPriorityAllowedAnnotation] @@ -298,15 +283,15 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error { if ok { val, err := strconv.ParseBool(enableNodePorts) if err != nil { - return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableNodePortsAnnotation, t.GetName())) + return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableNodePortsAnnotation, in.GetName())) } if dst.Spec.ServiceOptions == nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} + dst.Spec.ServiceOptions = &api.ServiceOptions{} } if dst.Spec.ServiceOptions.AllowedServices == nil { - dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{} + dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{} } dst.Spec.ServiceOptions.AllowedServices.NodePort = pointer.BoolPtr(val) @@ -316,15 +301,15 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error { if ok { val, err := strconv.ParseBool(enableExternalName) if err != nil { - return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableExternalNameAnnotation, t.GetName())) + return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableExternalNameAnnotation, in.GetName())) } if dst.Spec.ServiceOptions == nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} + dst.Spec.ServiceOptions = &api.ServiceOptions{} } if dst.Spec.ServiceOptions.AllowedServices == nil { - dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{} + dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{} } dst.Spec.ServiceOptions.AllowedServices.ExternalName = pointer.BoolPtr(val) @@ -334,23 +319,23 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error { if ok { val, err := strconv.ParseBool(loadBalancerService) if err != nil { - return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableLoadBalancerAnnotation, t.GetName())) + return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableLoadBalancerAnnotation, in.GetName())) } if dst.Spec.ServiceOptions == nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} + dst.Spec.ServiceOptions = &api.ServiceOptions{} } if dst.Spec.ServiceOptions.AllowedServices == nil { - dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{} + dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{} } dst.Spec.ServiceOptions.AllowedServices.LoadBalancer = pointer.BoolPtr(val) } // Status dst.Status = capsulev1beta1.TenantStatus{ - Size: t.Status.Size, - Namespaces: t.Status.Namespaces, + Size: in.Status.Size, + Namespaces: in.Status.Namespaces, } // Remove unneeded annotations delete(dst.ObjectMeta.Annotations, podAllowedImagePullPolicyAnnotation) @@ -381,7 +366,7 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error { } // nolint:gocognit,gocyclo,cyclop -func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) { +func (in *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) { ownersAnnotations := map[string][]string{ ownerGroupsAnnotation: nil, ownerUsersAnnotation: nil, @@ -402,7 +387,7 @@ func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) { for i, owner := range src.Spec.Owners { if i == 0 { - t.Spec.Owner = OwnerSpec{ + in.Spec.Owner = OwnerSpec{ Name: owner.Name, Kind: Kind(owner.Kind), } @@ -469,114 +454,89 @@ func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) { for k, v := range ownersAnnotations { if len(v) > 0 { - t.Annotations[k] = strings.Join(v, ",") + in.Annotations[k] = strings.Join(v, ",") } } for k, v := range proxyAnnotations { if len(v) > 0 { - t.Annotations[k] = strings.Join(v, ",") + in.Annotations[k] = strings.Join(v, ",") } } } -// nolint:gocyclo,cyclop -func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error { +//nolint:cyclop +func (in *Tenant) ConvertFrom(srcRaw conversion.Hub) error { src, ok := srcRaw.(*capsulev1beta1.Tenant) if !ok { return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", srcRaw) } // ObjectMeta - t.ObjectMeta = src.ObjectMeta + in.ObjectMeta = src.ObjectMeta // Spec if src.Spec.NamespaceOptions != nil && src.Spec.NamespaceOptions.Quota != nil { - t.Spec.NamespaceQuota = src.Spec.NamespaceOptions.Quota + in.Spec.NamespaceQuota = src.Spec.NamespaceOptions.Quota } - t.Spec.NodeSelector = src.Spec.NodeSelector + in.Spec.NodeSelector = src.Spec.NodeSelector - if t.Annotations == nil { - t.Annotations = make(map[string]string) + if in.Annotations == nil { + in.Annotations = make(map[string]string) } - t.convertV1Beta1OwnerToV1Alpha1(src) + in.convertV1Beta1OwnerToV1Alpha1(src) if src.Spec.NamespaceOptions != nil && src.Spec.NamespaceOptions.AdditionalMetadata != nil { - t.Spec.NamespacesMetadata = &AdditionalMetadataSpec{ - AdditionalLabels: src.Spec.NamespaceOptions.AdditionalMetadata.Labels, - AdditionalAnnotations: src.Spec.NamespaceOptions.AdditionalMetadata.Annotations, + in.Spec.NamespacesMetadata = &AdditionalMetadata{ + Labels: src.Spec.NamespaceOptions.AdditionalMetadata.Labels, + Annotations: src.Spec.NamespaceOptions.AdditionalMetadata.Annotations, } } if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AdditionalMetadata != nil { - t.Spec.ServicesMetadata = &AdditionalMetadataSpec{ - AdditionalLabels: src.Spec.ServiceOptions.AdditionalMetadata.Labels, - AdditionalAnnotations: src.Spec.ServiceOptions.AdditionalMetadata.Annotations, + in.Spec.ServicesMetadata = &AdditionalMetadata{ + Labels: src.Spec.ServiceOptions.AdditionalMetadata.Labels, + Annotations: src.Spec.ServiceOptions.AdditionalMetadata.Annotations, } } if src.Spec.StorageClasses != nil { - t.Spec.StorageClasses = &AllowedListSpec{ - Exact: src.Spec.StorageClasses.Exact, - Regex: src.Spec.StorageClasses.Regex, - } + in.Spec.StorageClasses = src.Spec.StorageClasses } - t.Annotations[ingressHostnameCollisionScope] = string(src.Spec.IngressOptions.HostnameCollisionScope) + in.Annotations[ingressHostnameCollisionScope] = string(src.Spec.IngressOptions.HostnameCollisionScope) if src.Spec.IngressOptions.AllowedClasses != nil { - t.Spec.IngressClasses = &AllowedListSpec{ - Exact: src.Spec.IngressOptions.AllowedClasses.Exact, - Regex: src.Spec.IngressOptions.AllowedClasses.Regex, - } + in.Spec.IngressClasses = src.Spec.IngressOptions.AllowedClasses } if src.Spec.IngressOptions.AllowedHostnames != nil { - t.Spec.IngressHostnames = &AllowedListSpec{ - Exact: src.Spec.IngressOptions.AllowedHostnames.Exact, - Regex: src.Spec.IngressOptions.AllowedHostnames.Regex, - } + in.Spec.IngressHostnames = src.Spec.IngressOptions.AllowedHostnames } if src.Spec.ContainerRegistries != nil { - t.Spec.ContainerRegistries = &AllowedListSpec{ - Exact: src.Spec.ContainerRegistries.Exact, - Regex: src.Spec.ContainerRegistries.Regex, - } + in.Spec.ContainerRegistries = src.Spec.ContainerRegistries } if len(src.Spec.NetworkPolicies.Items) > 0 { - t.Spec.NetworkPolicies = src.Spec.NetworkPolicies.Items + in.Spec.NetworkPolicies = src.Spec.NetworkPolicies.Items } if len(src.Spec.LimitRanges.Items) > 0 { - t.Spec.LimitRanges = src.Spec.LimitRanges.Items + in.Spec.LimitRanges = src.Spec.LimitRanges.Items } if len(src.Spec.ResourceQuota.Items) > 0 { - t.Annotations[resourceQuotaScopeAnnotation] = string(src.Spec.ResourceQuota.Scope) - t.Spec.ResourceQuota = src.Spec.ResourceQuota.Items + in.Annotations[resourceQuotaScopeAnnotation] = string(src.Spec.ResourceQuota.Scope) + in.Spec.ResourceQuota = src.Spec.ResourceQuota.Items } - if len(src.Spec.AdditionalRoleBindings) > 0 { - for _, rb := range src.Spec.AdditionalRoleBindings { - t.Spec.AdditionalRoleBindings = append(t.Spec.AdditionalRoleBindings, AdditionalRoleBindingsSpec{ - ClusterRoleName: rb.ClusterRoleName, - Subjects: rb.Subjects, - }) - } - } + in.Spec.AdditionalRoleBindings = src.Spec.AdditionalRoleBindings if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.ExternalServiceIPs != nil { - t.Spec.ExternalServiceIPs = &ExternalServiceIPsSpec{ - Allowed: make([]AllowedIP, len(src.Spec.ServiceOptions.ExternalServiceIPs.Allowed)), - } - - for i, IP := range src.Spec.ServiceOptions.ExternalServiceIPs.Allowed { - t.Spec.ExternalServiceIPs.Allowed[i] = AllowedIP(IP) - } + in.Spec.ExternalServiceIPs = src.Spec.ServiceOptions.ExternalServiceIPs } if len(src.Spec.ImagePullPolicies) != 0 { @@ -586,35 +546,35 @@ func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error { pullPolicies = append(pullPolicies, string(policy)) } - t.Annotations[podAllowedImagePullPolicyAnnotation] = strings.Join(pullPolicies, ",") + in.Annotations[podAllowedImagePullPolicyAnnotation] = strings.Join(pullPolicies, ",") } if src.Spec.PriorityClasses != nil { if len(src.Spec.PriorityClasses.Exact) != 0 { - t.Annotations[podPriorityAllowedAnnotation] = strings.Join(src.Spec.PriorityClasses.Exact, ",") + in.Annotations[podPriorityAllowedAnnotation] = strings.Join(src.Spec.PriorityClasses.Exact, ",") } if src.Spec.PriorityClasses.Regex != "" { - t.Annotations[podPriorityAllowedRegexAnnotation] = src.Spec.PriorityClasses.Regex + in.Annotations[podPriorityAllowedRegexAnnotation] = src.Spec.PriorityClasses.Regex } } if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AllowedServices != nil { if src.Spec.ServiceOptions.AllowedServices.NodePort != nil { - t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort) + in.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort) } if src.Spec.ServiceOptions.AllowedServices.ExternalName != nil { - t.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName) + in.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName) } if src.Spec.ServiceOptions.AllowedServices.LoadBalancer != nil { - t.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer) + in.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer) } } // Status - t.Status = TenantStatus{ + in.Status = TenantStatus{ Size: src.Status.Size, Namespaces: src.Status.Namespaces, } diff --git a/api/v1alpha1/conversion_hub_test.go b/api/v1alpha1/conversion_hub_test.go index 5c6f9311..275bfc1b 100644 --- a/api/v1alpha1/conversion_hub_test.go +++ b/api/v1alpha1/conversion_hub_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) // nolint:maintidx @@ -25,19 +26,19 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { nodeSelector := map[string]string{ "foo": "bar", } - v1alpha1AdditionalMetadataSpec := &AdditionalMetadataSpec{ - AdditionalLabels: map[string]string{ + v1alpha1AdditionalMetadataSpec := &AdditionalMetadata{ + Labels: map[string]string{ "foo": "bar", }, - AdditionalAnnotations: map[string]string{ + Annotations: map[string]string{ "foo": "bar", }, } - v1alpha1AllowedListSpec := &AllowedListSpec{ + v1alpha1AllowedListSpec := &api.AllowedListSpec{ Exact: []string{"foo", "bar"}, Regex: "^foo*", } - v1beta1AdditionalMetadataSpec := &capsulev1beta1.AdditionalMetadataSpec{ + v1beta1AdditionalMetadataSpec := &api.AdditionalMetadataSpec{ Labels: map[string]string{ "foo": "bar", }, @@ -49,18 +50,18 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { Quota: &namespaceQuota, AdditionalMetadata: v1beta1AdditionalMetadataSpec, } - v1beta1ServiceOptions := &capsulev1beta1.ServiceOptions{ + v1beta1ServiceOptions := &api.ServiceOptions{ AdditionalMetadata: v1beta1AdditionalMetadataSpec, - AllowedServices: &capsulev1beta1.AllowedServices{ + AllowedServices: &api.AllowedServices{ NodePort: pointer.BoolPtr(false), ExternalName: pointer.BoolPtr(false), LoadBalancer: pointer.BoolPtr(false), }, - ExternalServiceIPs: &capsulev1beta1.ExternalServiceIPsSpec{ - Allowed: []capsulev1beta1.AllowedIP{"192.168.0.1"}, + ExternalServiceIPs: &api.ExternalServiceIPsSpec{ + Allowed: []api.AllowedIP{"192.168.0.1"}, }, } - v1beta1AllowedListSpec := &capsulev1beta1.AllowedListSpec{ + v1beta1AllowedListSpec := &api.AllowedListSpec{ Exact: []string{"foo", "bar"}, Regex: "^foo*", } @@ -236,23 +237,23 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { ServiceOptions: v1beta1ServiceOptions, StorageClasses: v1beta1AllowedListSpec, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeDisabled, + HostnameCollisionScope: api.HostnameCollisionScopeDisabled, AllowedClasses: v1beta1AllowedListSpec, AllowedHostnames: v1beta1AllowedListSpec, }, ContainerRegistries: v1beta1AllowedListSpec, NodeSelector: nodeSelector, - NetworkPolicies: capsulev1beta1.NetworkPolicySpec{ + NetworkPolicies: api.NetworkPolicySpec{ Items: networkPolicies, }, - LimitRanges: capsulev1beta1.LimitRangesSpec{ + LimitRanges: api.LimitRangesSpec{ Items: limitRanges, }, - ResourceQuota: capsulev1beta1.ResourceQuotaSpec{ - Scope: capsulev1beta1.ResourceQuotaScopeNamespace, + ResourceQuota: api.ResourceQuotaSpec{ + Scope: api.ResourceQuotaScopeNamespace, Items: resourceQuotas, }, - AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{ + AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{ { ClusterRoleName: "crds-rolebinding", Subjects: []rbacv1.Subject{ @@ -264,8 +265,8 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { }, }, }, - ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always", "IfNotPresent"}, - PriorityClasses: &capsulev1beta1.AllowedListSpec{ + ImagePullPolicies: []api.ImagePullPolicySpec{"Always", "IfNotPresent"}, + PriorityClasses: &api.AllowedListSpec{ Exact: []string{"default"}, Regex: "^tier-.*$", }, @@ -323,7 +324,7 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { NetworkPolicies: networkPolicies, LimitRanges: limitRanges, ResourceQuota: resourceQuotas, - AdditionalRoleBindings: []AdditionalRoleBindingsSpec{ + AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{ { ClusterRoleName: "crds-rolebinding", Subjects: []rbacv1.Subject{ @@ -335,8 +336,8 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { }, }, }, - ExternalServiceIPs: &ExternalServiceIPsSpec{ - Allowed: []AllowedIP{"192.168.0.1"}, + ExternalServiceIPs: &api.ExternalServiceIPsSpec{ + Allowed: []api.AllowedIP{"192.168.0.1"}, }, }, Status: TenantStatus{ diff --git a/api/v1alpha1/tenant_func.go b/api/v1alpha1/tenant_func.go index 07a3d7b5..6342f2d1 100644 --- a/api/v1alpha1/tenant_func.go +++ b/api/v1alpha1/tenant_func.go @@ -9,24 +9,24 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (t *Tenant) IsCordoned() bool { - if v, ok := t.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" { +func (in *Tenant) IsCordoned() bool { + if v, ok := in.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" { return true } return false } -func (t *Tenant) IsFull() bool { +func (in *Tenant) IsFull() bool { // we don't have limits on assigned Namespaces - if t.Spec.NamespaceQuota == nil { + if in.Spec.NamespaceQuota == nil { return false } - return len(t.Status.Namespaces) >= int(*t.Spec.NamespaceQuota) + return len(in.Status.Namespaces) >= int(*in.Spec.NamespaceQuota) } -func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { +func (in *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { var l []string for _, ns := range namespaces { @@ -37,6 +37,6 @@ func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { sort.Strings(l) - t.Status.Namespaces = l - t.Status.Size = uint(len(l)) + in.Status.Namespaces = l + in.Status.Size = uint(len(l)) } diff --git a/api/v1alpha1/tenant_types.go b/api/v1alpha1/tenant_types.go index b9f0f786..f8509f3f 100644 --- a/api/v1alpha1/tenant_types.go +++ b/api/v1alpha1/tenant_types.go @@ -7,6 +7,8 @@ import ( corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/clastix/capsule/pkg/api" ) // TenantSpec defines the desired state of Tenant. @@ -15,18 +17,18 @@ type TenantSpec struct { //+kubebuilder:validation:Minimum=1 NamespaceQuota *int32 `json:"namespaceQuota,omitempty"` - NamespacesMetadata *AdditionalMetadataSpec `json:"namespacesMetadata,omitempty"` - ServicesMetadata *AdditionalMetadataSpec `json:"servicesMetadata,omitempty"` - StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"` - IngressClasses *AllowedListSpec `json:"ingressClasses,omitempty"` - IngressHostnames *AllowedListSpec `json:"ingressHostnames,omitempty"` - ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"` + NamespacesMetadata *AdditionalMetadata `json:"namespacesMetadata,omitempty"` + ServicesMetadata *AdditionalMetadata `json:"servicesMetadata,omitempty"` + StorageClasses *api.AllowedListSpec `json:"storageClasses,omitempty"` + IngressClasses *api.AllowedListSpec `json:"ingressClasses,omitempty"` + IngressHostnames *api.AllowedListSpec `json:"ingressHostnames,omitempty"` + ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"` NodeSelector map[string]string `json:"nodeSelector,omitempty"` NetworkPolicies []networkingv1.NetworkPolicySpec `json:"networkPolicies,omitempty"` LimitRanges []corev1.LimitRangeSpec `json:"limitRanges,omitempty"` ResourceQuota []corev1.ResourceQuotaSpec `json:"resourceQuotas,omitempty"` - AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` - ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"` + AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` + ExternalServiceIPs *api.ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"` } // TenantStatus defines the observed state of Tenant. diff --git a/api/v1alpha1/tenant_webhook.go b/api/v1alpha1/tenant_webhook.go index 13443adb..0df967e9 100644 --- a/api/v1alpha1/tenant_webhook.go +++ b/api/v1alpha1/tenant_webhook.go @@ -4,18 +4,18 @@ package v1alpha1 import ( - "io/ioutil" + "os" ctrl "sigs.k8s.io/controller-runtime" ) -func (t *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error { - certData, _ := ioutil.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt") +func (in *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error { + certData, _ := os.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt") if len(certData) == 0 { return nil } return ctrl.NewWebhookManagedBy(mgr). - For(t). + For(in). Complete() } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index efca1518..db654794 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -9,24 +9,24 @@ package v1alpha1 import ( + "github.com/clastix/capsule/pkg/api" corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" + "k8s.io/api/networking/v1" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { +func (in *AdditionalMetadata) DeepCopyInto(out *AdditionalMetadata) { *out = *in - if in.AdditionalLabels != nil { - in, out := &in.AdditionalLabels, &out.AdditionalLabels + if in.Labels != nil { + in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } - if in.AdditionalAnnotations != nil { - in, out := &in.AdditionalAnnotations, &out.AdditionalAnnotations + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val @@ -34,52 +34,12 @@ func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec. -func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadata. +func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata { if in == nil { return nil } - out := new(AdditionalMetadataSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) { - *out = *in - if in.Subjects != nil { - in, out := &in.Subjects, &out.Subjects - *out = make([]v1.Subject, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec. -func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { - if in == nil { - return nil - } - out := new(AdditionalRoleBindingsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) { - *out = *in - if in.Exact != nil { - in, out := &in.Exact, &out.Exact - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec. -func (in *AllowedListSpec) DeepCopy() *AllowedListSpec { - if in == nil { - return nil - } - out := new(AllowedListSpec) + out := new(AdditionalMetadata) in.DeepCopyInto(out) return out } @@ -162,26 +122,6 @@ func (in *CapsuleConfigurationSpec) DeepCopy() *CapsuleConfigurationSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { - *out = *in - if in.Allowed != nil { - in, out := &in.Allowed, &out.Allowed - *out = make([]AllowedIP, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec. -func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec { - if in == nil { - return nil - } - out := new(ExternalServiceIPsSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) { *out = *in @@ -267,32 +207,32 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.NamespacesMetadata != nil { in, out := &in.NamespacesMetadata, &out.NamespacesMetadata - *out = new(AdditionalMetadataSpec) + *out = new(AdditionalMetadata) (*in).DeepCopyInto(*out) } if in.ServicesMetadata != nil { in, out := &in.ServicesMetadata, &out.ServicesMetadata - *out = new(AdditionalMetadataSpec) + *out = new(AdditionalMetadata) (*in).DeepCopyInto(*out) } if in.StorageClasses != nil { in, out := &in.StorageClasses, &out.StorageClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.IngressClasses != nil { in, out := &in.IngressClasses, &out.IngressClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.IngressHostnames != nil { in, out := &in.IngressHostnames, &out.IngressHostnames - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.ContainerRegistries != nil { in, out := &in.ContainerRegistries, &out.ContainerRegistries - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.NodeSelector != nil { @@ -304,7 +244,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.NetworkPolicies != nil { in, out := &in.NetworkPolicies, &out.NetworkPolicies - *out = make([]networkingv1.NetworkPolicySpec, len(*in)) + *out = make([]v1.NetworkPolicySpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -325,14 +265,14 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.AdditionalRoleBindings != nil { in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings - *out = make([]AdditionalRoleBindingsSpec, len(*in)) + *out = make([]api.AdditionalRoleBindingsSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.ExternalServiceIPs != nil { in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs - *out = new(ExternalServiceIPsSpec) + *out = new(api.ExternalServiceIPsSpec) (*in).DeepCopyInto(*out) } } diff --git a/api/v1beta1/additional_role_bindings.go b/api/v1beta1/additional_role_bindings.go deleted file mode 100644 index f71e3cec..00000000 --- a/api/v1beta1/additional_role_bindings.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -import rbacv1 "k8s.io/api/rbac/v1" - -type AdditionalRoleBindingsSpec struct { - ClusterRoleName string `json:"clusterRoleName"` - // kubebuilder:validation:Minimum=1 - Subjects []rbacv1.Subject `json:"subjects"` -} diff --git a/api/v1beta1/allowed_list.go b/api/v1beta1/allowed_list.go deleted file mode 100644 index d7e24cac..00000000 --- a/api/v1beta1/allowed_list.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -//nolint:dupl -package v1beta1 - -import ( - "regexp" - "sort" - "strings" -) - -type AllowedListSpec struct { - Exact []string `json:"allowed,omitempty"` - Regex string `json:"allowedRegex,omitempty"` -} - -func (in *AllowedListSpec) ExactMatch(value string) (ok bool) { - if len(in.Exact) > 0 { - sort.SliceStable(in.Exact, func(i, j int) bool { - return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) - }) - - i := sort.SearchStrings(in.Exact, value) - - ok = i < len(in.Exact) && in.Exact[i] == value - } - - return -} - -func (in AllowedListSpec) RegexMatch(value string) (ok bool) { - if len(in.Regex) > 0 { - ok = regexp.MustCompile(in.Regex).MatchString(value) - } - - return -} diff --git a/api/v1beta1/deny_wildcard.go b/api/v1beta1/deny_wildcard.go index 10528f5e..76460942 100644 --- a/api/v1beta1/deny_wildcard.go +++ b/api/v1beta1/deny_wildcard.go @@ -7,8 +7,8 @@ const ( DenyWildcard = "capsule.clastix.io/deny-wildcard" ) -func (t *Tenant) IsWildcardDenied() bool { - if v, ok := t.Annotations[DenyWildcard]; ok && v == "true" { +func (in *Tenant) IsWildcardDenied() bool { + if v, ok := in.Annotations[DenyWildcard]; ok && v == "true" { return true } diff --git a/api/v1beta1/forbidden_list.go b/api/v1beta1/forbidden_list.go deleted file mode 100644 index 7816dd5b..00000000 --- a/api/v1beta1/forbidden_list.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -//nolint:dupl -package v1beta1 - -import ( - "regexp" - "sort" - "strings" -) - -type ForbiddenListSpec struct { - Exact []string `json:"denied,omitempty"` - Regex string `json:"deniedRegex,omitempty"` -} - -func (in *ForbiddenListSpec) ExactMatch(value string) (ok bool) { - if len(in.Exact) > 0 { - sort.SliceStable(in.Exact, func(i, j int) bool { - return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) - }) - - i := sort.SearchStrings(in.Exact, value) - - ok = i < len(in.Exact) && in.Exact[i] == value - } - - return -} - -func (in ForbiddenListSpec) RegexMatch(value string) (ok bool) { - if len(in.Regex) > 0 { - ok = regexp.MustCompile(in.Regex).MatchString(value) - } - - return -} diff --git a/api/v1beta1/forbidden_list_test.go b/api/v1beta1/forbidden_list_test.go deleted file mode 100644 index ef80d762..00000000 --- a/api/v1beta1/forbidden_list_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 -//nolint:dupl -package v1beta1 - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestForbiddenListSpec_ExactMatch(t *testing.T) { - type tc struct { - In []string - True []string - False []string - } - - for _, tc := range []tc{ - { - []string{"foo", "bar", "bizz", "buzz"}, - []string{"foo", "bar", "bizz", "buzz"}, - []string{"bing", "bong"}, - }, - { - []string{"one", "two", "three"}, - []string{"one", "two", "three"}, - []string{"a", "b", "c"}, - }, - { - nil, - nil, - []string{"any", "value"}, - }, - } { - a := ForbiddenListSpec{ - Exact: tc.In, - } - - for _, ok := range tc.True { - assert.True(t, a.ExactMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.ExactMatch(ko)) - } - } -} - -func TestForbiddenListSpec_RegexMatch(t *testing.T) { - type tc struct { - Regex string - True []string - False []string - } - - for _, tc := range []tc{ - {`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}}, - {``, nil, []string{"any", "value"}}, - } { - a := ForbiddenListSpec{ - Regex: tc.Regex, - } - - for _, ok := range tc.True { - assert.True(t, a.RegexMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.RegexMatch(ko)) - } - } -} diff --git a/api/v1beta1/hostname_collision_scope.go b/api/v1beta1/hostname_collision_scope.go deleted file mode 100644 index 6bed62b9..00000000 --- a/api/v1beta1/hostname_collision_scope.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -const ( - HostnameCollisionScopeCluster HostnameCollisionScope = "Cluster" - HostnameCollisionScopeTenant HostnameCollisionScope = "Tenant" - HostnameCollisionScopeNamespace HostnameCollisionScope = "Namespace" - HostnameCollisionScopeDisabled HostnameCollisionScope = "Disabled" -) - -// +kubebuilder:validation:Enum=Cluster;Tenant;Namespace;Disabled -type HostnameCollisionScope string diff --git a/api/v1beta1/image_pull_policy.go b/api/v1beta1/image_pull_policy.go deleted file mode 100644 index 35076840..00000000 --- a/api/v1beta1/image_pull_policy.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -// +kubebuilder:validation:Enum=Always;Never;IfNotPresent -type ImagePullPolicySpec string - -func (i ImagePullPolicySpec) String() string { - return string(i) -} diff --git a/api/v1beta1/ingress_options.go b/api/v1beta1/ingress_options.go index d748e472..ab4baad7 100644 --- a/api/v1beta1/ingress_options.go +++ b/api/v1beta1/ingress_options.go @@ -3,9 +3,13 @@ package v1beta1 +import ( + "github.com/clastix/capsule/pkg/api" +) + type IngressOptions struct { // Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. - AllowedClasses *AllowedListSpec `json:"allowedClasses,omitempty"` + AllowedClasses *api.AllowedListSpec `json:"allowedClasses,omitempty"` // Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. // // @@ -18,7 +22,7 @@ type IngressOptions struct { // // Optional. // +kubebuilder:default=Disabled - HostnameCollisionScope HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"` + HostnameCollisionScope api.HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"` // Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. - AllowedHostnames *AllowedListSpec `json:"allowedHostnames,omitempty"` + AllowedHostnames *api.AllowedListSpec `json:"allowedHostnames,omitempty"` } diff --git a/api/v1beta1/limit_ranges.go b/api/v1beta1/limit_ranges.go deleted file mode 100644 index 81d0e431..00000000 --- a/api/v1beta1/limit_ranges.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -import corev1 "k8s.io/api/core/v1" - -type LimitRangesSpec struct { - Items []corev1.LimitRangeSpec `json:"items,omitempty"` -} diff --git a/api/v1beta1/namespace_options.go b/api/v1beta1/namespace_options.go index cf35753b..4135c4d8 100644 --- a/api/v1beta1/namespace_options.go +++ b/api/v1beta1/namespace_options.go @@ -1,57 +1,61 @@ package v1beta1 -import "strings" +import ( + "strings" + + "github.com/clastix/capsule/pkg/api" +) type NamespaceOptions struct { //+kubebuilder:validation:Minimum=1 // Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. Quota *int32 `json:"quota,omitempty"` // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. - AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` } -func (t *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool { - if _, ok := t.Annotations[ForbiddenNamespaceLabelsAnnotation]; ok { +func (in *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool { + if _, ok := in.Annotations[ForbiddenNamespaceLabelsAnnotation]; ok { return true } - if _, ok := t.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation]; ok { + if _, ok := in.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation]; ok { return true } return false } -func (t *Tenant) hasForbiddenNamespaceAnnotationsAnnotations() bool { - if _, ok := t.Annotations[ForbiddenNamespaceAnnotationsAnnotation]; ok { +func (in *Tenant) hasForbiddenNamespaceAnnotationsAnnotations() bool { + if _, ok := in.Annotations[ForbiddenNamespaceAnnotationsAnnotation]; ok { return true } - if _, ok := t.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok { + if _, ok := in.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok { return true } return false } -func (t *Tenant) ForbiddenUserNamespaceLabels() *ForbiddenListSpec { - if !t.hasForbiddenNamespaceLabelsAnnotations() { +func (in *Tenant) ForbiddenUserNamespaceLabels() *api.ForbiddenListSpec { + if !in.hasForbiddenNamespaceLabelsAnnotations() { return nil } - return &ForbiddenListSpec{ - Exact: strings.Split(t.Annotations[ForbiddenNamespaceLabelsAnnotation], ","), - Regex: t.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation], + return &api.ForbiddenListSpec{ + Exact: strings.Split(in.Annotations[ForbiddenNamespaceLabelsAnnotation], ","), + Regex: in.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation], } } -func (t *Tenant) ForbiddenUserNamespaceAnnotations() *ForbiddenListSpec { - if !t.hasForbiddenNamespaceAnnotationsAnnotations() { +func (in *Tenant) ForbiddenUserNamespaceAnnotations() *api.ForbiddenListSpec { + if !in.hasForbiddenNamespaceAnnotationsAnnotations() { return nil } - return &ForbiddenListSpec{ - Exact: strings.Split(t.Annotations[ForbiddenNamespaceAnnotationsAnnotation], ","), - Regex: t.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation], + return &api.ForbiddenListSpec{ + Exact: strings.Split(in.Annotations[ForbiddenNamespaceAnnotationsAnnotation], ","), + Regex: in.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation], } } diff --git a/api/v1beta1/owner_list.go b/api/v1beta1/owner_list.go index 355ab7e1..dd0c4209 100644 --- a/api/v1beta1/owner_list.go +++ b/api/v1beta1/owner_list.go @@ -6,14 +6,14 @@ import ( type OwnerListSpec []OwnerSpec -func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) { - sort.Sort(ByKindAndName(o)) - i := sort.Search(len(o), func(i int) bool { - return o[i].Kind >= kind && o[i].Name >= name +func (in OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) { + sort.Sort(ByKindAndName(in)) + i := sort.Search(len(in), func(i int) bool { + return in[i].Kind >= kind && in[i].Name >= name }) - if i < len(o) && o[i].Kind == kind && o[i].Name == name { - return o[i] + if i < len(in) && in[i].Kind == kind && in[i].Name == name { + return in[i] } return @@ -21,18 +21,18 @@ func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) type ByKindAndName OwnerListSpec -func (b ByKindAndName) Len() int { - return len(b) +func (in ByKindAndName) Len() int { + return len(in) } -func (b ByKindAndName) Less(i, j int) bool { - if b[i].Kind.String() != b[j].Kind.String() { - return b[i].Kind.String() < b[j].Kind.String() +func (in ByKindAndName) Less(i, j int) bool { + if in[i].Kind.String() != in[j].Kind.String() { + return in[i].Kind.String() < in[j].Kind.String() } - return b[i].Name < b[j].Name + return in[i].Name < in[j].Name } -func (b ByKindAndName) Swap(i, j int) { - b[i], b[j] = b[j], b[i] +func (in ByKindAndName) Swap(i, j int) { + in[i], in[j] = in[j], in[i] } diff --git a/api/v1beta1/owner_role.go b/api/v1beta1/owner_role.go index dc73309d..27123c30 100644 --- a/api/v1beta1/owner_role.go +++ b/api/v1beta1/owner_role.go @@ -19,7 +19,7 @@ const ( // 2. the overall length of the annotation key that is exceeding 63 characters // For emails, the symbol @ can be replaced with the placeholder __AT__. // For the latter one, the index of the owner can be used to force the retrieval. -func (in OwnerSpec) GetRoles(tenant Tenant, index int) []string { +func (in *OwnerSpec) GetRoles(tenant Tenant, index int) []string { for key, value := range tenant.GetAnnotations() { if !strings.HasPrefix(key, fmt.Sprintf("%s/", ClusterRoleNamesAnnotation)) { continue @@ -41,7 +41,7 @@ func (in OwnerSpec) GetRoles(tenant Tenant, index int) []string { return []string{"admin", "capsule-namespace-deleter"} } -func (in OwnerSpec) convertMap() map[string]string { +func (in *OwnerSpec) convertMap() map[string]string { return map[string]string{ "__AT__": "@", } diff --git a/api/v1beta1/service_allowed_ips.go b/api/v1beta1/service_allowed_ips.go deleted file mode 100644 index 5dd65ba0..00000000 --- a/api/v1beta1/service_allowed_ips.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" -type AllowedIP string - -type ExternalServiceIPsSpec struct { - Allowed []AllowedIP `json:"allowed"` -} diff --git a/api/v1beta1/service_options.go b/api/v1beta1/service_options.go index dfdcc265..636a2f7f 100644 --- a/api/v1beta1/service_options.go +++ b/api/v1beta1/service_options.go @@ -3,11 +3,15 @@ package v1beta1 +import ( + "github.com/clastix/capsule/pkg/api" +) + type ServiceOptions struct { // Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. - AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` // Block or deny certain type of Services. Optional. - AllowedServices *AllowedServices `json:"allowedServices,omitempty"` + AllowedServices *api.AllowedServices `json:"allowedServices,omitempty"` // Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. - ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalIPs,omitempty"` + ExternalServiceIPs *api.ExternalServiceIPsSpec `json:"externalIPs,omitempty"` } diff --git a/api/v1beta1/tenant_annotations.go b/api/v1beta1/tenant_annotations.go index 9c7f3ad7..8c21e925 100644 --- a/api/v1beta1/tenant_annotations.go +++ b/api/v1beta1/tenant_annotations.go @@ -9,12 +9,6 @@ import ( ) const ( - AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes" - AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp" - AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes" - AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp" - AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries" - AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp" ForbiddenNamespaceLabelsAnnotation = "capsule.clastix.io/forbidden-namespace-labels" ForbiddenNamespaceLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-labels-regexp" ForbiddenNamespaceAnnotationsAnnotation = "capsule.clastix.io/forbidden-namespace-annotations" diff --git a/api/v1beta1/tenant_func.go b/api/v1beta1/tenant_func.go index 2bb43969..8111c028 100644 --- a/api/v1beta1/tenant_func.go +++ b/api/v1beta1/tenant_func.go @@ -9,24 +9,24 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (t *Tenant) IsCordoned() bool { - if v, ok := t.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" { +func (in *Tenant) IsCordoned() bool { + if v, ok := in.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" { return true } return false } -func (t *Tenant) IsFull() bool { +func (in *Tenant) IsFull() bool { // we don't have limits on assigned Namespaces - if t.Spec.NamespaceOptions == nil || t.Spec.NamespaceOptions.Quota == nil { + if in.Spec.NamespaceOptions == nil || in.Spec.NamespaceOptions.Quota == nil { return false } - return len(t.Status.Namespaces) >= int(*t.Spec.NamespaceOptions.Quota) + return len(in.Status.Namespaces) >= int(*in.Spec.NamespaceOptions.Quota) } -func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { +func (in *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { var l []string for _, ns := range namespaces { @@ -37,10 +37,10 @@ func (t *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { sort.Strings(l) - t.Status.Namespaces = l - t.Status.Size = uint(len(l)) + in.Status.Namespaces = l + in.Status.Size = uint(len(l)) } -func (t *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings { - return t.Spec.Owners.FindOwner(name, kind).ProxyOperations +func (in *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings { + return in.Spec.Owners.FindOwner(name, kind).ProxyOperations } diff --git a/api/v1beta1/tenant_labels.go b/api/v1beta1/tenant_labels.go deleted file mode 100644 index 836e6810..00000000 --- a/api/v1beta1/tenant_labels.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta1 - -import ( - "fmt" - - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -func GetTypeLabel(t runtime.Object) (label string, err error) { - switch v := t.(type) { - case *Tenant: - return "capsule.clastix.io/tenant", nil - case *corev1.LimitRange: - return "capsule.clastix.io/limit-range", nil - case *networkingv1.NetworkPolicy: - return "capsule.clastix.io/network-policy", nil - case *corev1.ResourceQuota: - return "capsule.clastix.io/resource-quota", nil - case *rbacv1.RoleBinding: - return "capsule.clastix.io/role-binding", nil - default: - err = fmt.Errorf("type %T is not mapped as Capsule label recognized", v) - } - - return -} diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 740c6f5a..9e17b53e 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -5,6 +5,8 @@ package v1beta1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/clastix/capsule/pkg/api" ) // TenantSpec defines the desired state of Tenant. @@ -14,27 +16,27 @@ type TenantSpec struct { // Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"` // Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. - ServiceOptions *ServiceOptions `json:"serviceOptions,omitempty"` + ServiceOptions *api.ServiceOptions `json:"serviceOptions,omitempty"` // Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. - StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"` + StorageClasses *api.AllowedListSpec `json:"storageClasses,omitempty"` // Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. IngressOptions IngressOptions `json:"ingressOptions,omitempty"` // Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. - ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"` + ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"` // Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. NodeSelector map[string]string `json:"nodeSelector,omitempty"` // Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. - NetworkPolicies NetworkPolicySpec `json:"networkPolicies,omitempty"` + NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitempty"` // Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. - LimitRanges LimitRangesSpec `json:"limitRanges,omitempty"` + LimitRanges api.LimitRangesSpec `json:"limitRanges,omitempty"` // Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. - ResourceQuota ResourceQuotaSpec `json:"resourceQuotas,omitempty"` + ResourceQuota api.ResourceQuotaSpec `json:"resourceQuotas,omitempty"` // Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. - AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` + AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` // Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. - ImagePullPolicies []ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` + ImagePullPolicies []api.ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` // Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. - PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"` + PriorityClasses *api.AllowedListSpec `json:"priorityClasses,omitempty"` } //+kubebuilder:object:root=true @@ -56,7 +58,7 @@ type Tenant struct { Status TenantStatus `json:"status,omitempty"` } -func (t *Tenant) Hub() {} +func (in *Tenant) Hub() {} //+kubebuilder:object:root=true diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index f410020d..5ae9834b 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -9,81 +9,10 @@ package v1beta1 import ( - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" + "github.com/clastix/capsule/pkg/api" + runtime "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec. -func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec { - if in == nil { - return nil - } - out := new(AdditionalMetadataSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) { - *out = *in - if in.Subjects != nil { - in, out := &in.Subjects, &out.Subjects - *out = make([]v1.Subject, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec. -func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { - if in == nil { - return nil - } - out := new(AdditionalRoleBindingsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) { - *out = *in - if in.Exact != nil { - in, out := &in.Exact, &out.Exact - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec. -func (in *AllowedListSpec) DeepCopy() *AllowedListSpec { - if in == nil { - return nil - } - out := new(AllowedListSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AllowedServices) DeepCopyInto(out *AllowedServices) { *out = *in @@ -135,57 +64,17 @@ func (in ByKindAndName) DeepCopy() ByKindAndName { return *out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { - *out = *in - if in.Allowed != nil { - in, out := &in.Allowed, &out.Allowed - *out = make([]AllowedIP, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec. -func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec { - if in == nil { - return nil - } - out := new(ExternalServiceIPsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ForbiddenListSpec) DeepCopyInto(out *ForbiddenListSpec) { - *out = *in - if in.Exact != nil { - in, out := &in.Exact, &out.Exact - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForbiddenListSpec. -func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec { - if in == nil { - return nil - } - out := new(ForbiddenListSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressOptions) DeepCopyInto(out *IngressOptions) { *out = *in if in.AllowedClasses != nil { in, out := &in.AllowedClasses, &out.AllowedClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.AllowedHostnames != nil { in, out := &in.AllowedHostnames, &out.AllowedHostnames - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } } @@ -200,28 +89,6 @@ func (in *IngressOptions) DeepCopy() *IngressOptions { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]corev1.LimitRangeSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangesSpec. -func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec { - if in == nil { - return nil - } - out := new(LimitRangesSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) { *out = *in @@ -232,7 +99,7 @@ func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) { } if in.AdditionalMetadata != nil { in, out := &in.AdditionalMetadata, &out.AdditionalMetadata - *out = new(AdditionalMetadataSpec) + *out = new(api.AdditionalMetadataSpec) (*in).DeepCopyInto(*out) } } @@ -247,28 +114,6 @@ func (in *NamespaceOptions) DeepCopy() *NamespaceOptions { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]networkingv1.NetworkPolicySpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec. -func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { - if in == nil { - return nil - } - out := new(NetworkPolicySpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NonLimitedResourceError) DeepCopyInto(out *NonLimitedResourceError) { *out = *in @@ -347,44 +192,22 @@ func (in *ProxySettings) DeepCopy() *ProxySettings { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]corev1.ResourceQuotaSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec. -func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { - if in == nil { - return nil - } - out := new(ResourceQuotaSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) { *out = *in if in.AdditionalMetadata != nil { in, out := &in.AdditionalMetadata, &out.AdditionalMetadata - *out = new(AdditionalMetadataSpec) + *out = new(api.AdditionalMetadataSpec) (*in).DeepCopyInto(*out) } if in.AllowedServices != nil { in, out := &in.AllowedServices, &out.AllowedServices - *out = new(AllowedServices) + *out = new(api.AllowedServices) (*in).DeepCopyInto(*out) } if in.ExternalServiceIPs != nil { in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs - *out = new(ExternalServiceIPsSpec) + *out = new(api.ExternalServiceIPsSpec) (*in).DeepCopyInto(*out) } } @@ -475,18 +298,18 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.ServiceOptions != nil { in, out := &in.ServiceOptions, &out.ServiceOptions - *out = new(ServiceOptions) + *out = new(api.ServiceOptions) (*in).DeepCopyInto(*out) } if in.StorageClasses != nil { in, out := &in.StorageClasses, &out.StorageClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } in.IngressOptions.DeepCopyInto(&out.IngressOptions) if in.ContainerRegistries != nil { in, out := &in.ContainerRegistries, &out.ContainerRegistries - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.NodeSelector != nil { @@ -501,19 +324,19 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { in.ResourceQuota.DeepCopyInto(&out.ResourceQuota) if in.AdditionalRoleBindings != nil { in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings - *out = make([]AdditionalRoleBindingsSpec, len(*in)) + *out = make([]api.AdditionalRoleBindingsSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.ImagePullPolicies != nil { in, out := &in.ImagePullPolicies, &out.ImagePullPolicies - *out = make([]ImagePullPolicySpec, len(*in)) + *out = make([]api.ImagePullPolicySpec, len(*in)) copy(*out, *in) } if in.PriorityClasses != nil { in, out := &in.PriorityClasses, &out.PriorityClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } } diff --git a/api/v1beta2/allowed_list_test.go b/api/v1beta2/allowed_list_test.go deleted file mode 100644 index 77754933..00000000 --- a/api/v1beta2/allowed_list_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 -//nolint:dupl -package v1beta2 - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAllowedListSpec_ExactMatch(t *testing.T) { - type tc struct { - In []string - True []string - False []string - } - - for _, tc := range []tc{ - { - []string{"foo", "bar", "bizz", "buzz"}, - []string{"foo", "bar", "bizz", "buzz"}, - []string{"bing", "bong"}, - }, - { - []string{"one", "two", "three"}, - []string{"one", "two", "three"}, - []string{"a", "b", "c"}, - }, - { - nil, - nil, - []string{"any", "value"}, - }, - } { - a := AllowedListSpec{ - Exact: tc.In, - } - - for _, ok := range tc.True { - assert.True(t, a.ExactMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.ExactMatch(ko)) - } - } -} - -func TestAllowedListSpec_RegexMatch(t *testing.T) { - type tc struct { - Regex string - True []string - False []string - } - - for _, tc := range []tc{ - {`first-\w+-pattern`, []string{"first-date-pattern", "first-year-pattern"}, []string{"broken", "first-year", "second-date-pattern"}}, - {``, nil, []string{"any", "value"}}, - } { - a := AllowedListSpec{ - Regex: tc.Regex, - } - - for _, ok := range tc.True { - assert.True(t, a.RegexMatch(ok)) - } - - for _, ko := range tc.False { - assert.False(t, a.RegexMatch(ko)) - } - } -} diff --git a/api/v1beta2/capsuleconfiguration_funcs.go b/api/v1beta2/capsuleconfiguration_convertion_hub.go similarity index 84% rename from api/v1beta2/capsuleconfiguration_funcs.go rename to api/v1beta2/capsuleconfiguration_convertion_hub.go index 7e029cad..6f4c02b5 100644 --- a/api/v1beta2/capsuleconfiguration_funcs.go +++ b/api/v1beta2/capsuleconfiguration_convertion_hub.go @@ -30,10 +30,21 @@ func (in *CapsuleConfiguration) ConvertTo(raw conversion.Hub) error { } if in.Spec.NodeMetadata != nil { - annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenLabels.Exact, ",") - annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenLabels.Regex - annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact, ",") - annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenAnnotations.Regex + if len(in.Spec.NodeMetadata.ForbiddenLabels.Exact) > 0 { + annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenLabels.Exact, ",") + } + + if len(in.Spec.NodeMetadata.ForbiddenLabels.Regex) > 0 { + annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenLabels.Regex + } + + if len(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact) > 0 { + annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact, ",") + } + + if len(in.Spec.NodeMetadata.ForbiddenAnnotations.Regex) > 0 { + annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenAnnotations.Regex + } } annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName] = fmt.Sprintf("%t", in.Spec.EnableTLSReconciler) diff --git a/api/v1beta2/capsuleconfiguration_types.go b/api/v1beta2/capsuleconfiguration_types.go index 207cd00b..d9a56424 100644 --- a/api/v1beta2/capsuleconfiguration_types.go +++ b/api/v1beta2/capsuleconfiguration_types.go @@ -5,6 +5,8 @@ package v1beta2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/clastix/capsule/pkg/api" ) // CapsuleConfigurationSpec defines the Capsule configuration. @@ -32,9 +34,9 @@ type CapsuleConfigurationSpec struct { type NodeMetadata struct { // Define the labels that a Tenant Owner cannot set for their nodes. - ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels"` + ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels"` // Define the annotations that a Tenant Owner cannot set for their nodes. - ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations"` + ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations"` } type CapsuleResources struct { diff --git a/api/v1beta2/conversion_hub.go b/api/v1beta2/conversion_hub.go deleted file mode 100644 index 0c57e00c..00000000 --- a/api/v1beta2/conversion_hub.go +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -//nolint:nestif,cyclop,maintidx -package v1beta2 - -import ( - "fmt" - "strconv" - "strings" - - "sigs.k8s.io/controller-runtime/pkg/conversion" - - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" -) - -//nolint:gocyclo -func (in *Tenant) ConvertFrom(raw conversion.Hub) error { - src, ok := raw.(*capsulev1beta1.Tenant) - if !ok { - return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) - } - - annotations := src.GetAnnotations() - if annotations == nil { - annotations = map[string]string{} - } - - in.ObjectMeta = src.ObjectMeta - in.Spec.Owners = make(OwnerListSpec, 0, len(src.Spec.Owners)) - - for index, owner := range src.Spec.Owners { - proxySettings := make([]ProxySettings, 0, len(owner.ProxyOperations)) - - for _, proxyOp := range owner.ProxyOperations { - ops := make([]ProxyOperation, 0, len(proxyOp.Operations)) - - for _, op := range proxyOp.Operations { - ops = append(ops, ProxyOperation(op)) - } - - proxySettings = append(proxySettings, ProxySettings{ - Kind: ProxyServiceKind(proxyOp.Kind), - Operations: ops, - }) - } - - in.Spec.Owners = append(in.Spec.Owners, OwnerSpec{ - Kind: OwnerKind(owner.Kind), - Name: owner.Name, - ClusterRoles: owner.GetRoles(*src, index), - ProxyOperations: proxySettings, - }) - } - - if nsOpts := src.Spec.NamespaceOptions; nsOpts != nil { - in.Spec.NamespaceOptions = &NamespaceOptions{} - - in.Spec.NamespaceOptions.Quota = src.Spec.NamespaceOptions.Quota - - if nsOpts.AdditionalMetadata != nil { - in.Spec.NamespaceOptions.AdditionalMetadata = &AdditionalMetadataSpec{} - - in.Spec.NamespaceOptions.AdditionalMetadata.Annotations = nsOpts.AdditionalMetadata.Annotations - in.Spec.NamespaceOptions.AdditionalMetadata.Labels = nsOpts.AdditionalMetadata.Labels - } - - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation]; found { - in.Spec.NamespaceOptions.ForbiddenLabels.Exact = strings.Split(value, ",") - - delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsAnnotation) - } - - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation]; found { - in.Spec.NamespaceOptions.ForbiddenLabels.Regex = value - - delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation) - } - - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation]; found { - in.Spec.NamespaceOptions.ForbiddenAnnotations.Exact = strings.Split(value, ",") - - delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation) - } - - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found { - in.Spec.NamespaceOptions.ForbiddenAnnotations.Regex = value - - delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation) - } - } - - if svcOpts := src.Spec.ServiceOptions; svcOpts != nil { - in.Spec.ServiceOptions = &ServiceOptions{} - - if metadata := svcOpts.AdditionalMetadata; metadata != nil { - in.Spec.ServiceOptions.AdditionalMetadata = &AdditionalMetadataSpec{ - Labels: metadata.Labels, - Annotations: metadata.Annotations, - } - } - - if types := svcOpts.AllowedServices; types != nil { - in.Spec.ServiceOptions.AllowedServices = &AllowedServices{ - NodePort: types.NodePort, - ExternalName: types.ExternalName, - LoadBalancer: types.LoadBalancer, - } - } - - if externalIPs := svcOpts.ExternalServiceIPs; externalIPs != nil { - allowed := make([]AllowedIP, 0, len(externalIPs.Allowed)) - - for _, ip := range externalIPs.Allowed { - allowed = append(allowed, AllowedIP(ip)) - } - - in.Spec.ServiceOptions.ExternalServiceIPs = &ExternalServiceIPsSpec{ - Allowed: allowed, - } - } - } - - if sc := src.Spec.StorageClasses; sc != nil { - in.Spec.StorageClasses = &AllowedListSpec{ - Exact: sc.Exact, - Regex: sc.Regex, - } - } - - if scope := src.Spec.IngressOptions.HostnameCollisionScope; len(scope) > 0 { - in.Spec.IngressOptions.HostnameCollisionScope = HostnameCollisionScope(scope) - } - - v, found := annotations[capsulev1beta1.DenyWildcard] - if found { - value, err := strconv.ParseBool(v) - if err == nil { - in.Spec.IngressOptions.AllowWildcardHostnames = value - - delete(annotations, capsulev1beta1.DenyWildcard) - } - } - - if ingressClass := src.Spec.IngressOptions.AllowedClasses; ingressClass != nil { - in.Spec.IngressOptions.AllowedClasses = &AllowedListSpec{ - Exact: ingressClass.Exact, - Regex: ingressClass.Regex, - } - } - - if hostnames := src.Spec.IngressOptions.AllowedHostnames; hostnames != nil { - in.Spec.IngressOptions.AllowedClasses = &AllowedListSpec{ - Exact: hostnames.Exact, - Regex: hostnames.Regex, - } - } - - if allowed := src.Spec.ContainerRegistries; allowed != nil { - in.Spec.ContainerRegistries = &AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - in.Spec.NodeSelector = src.Spec.NodeSelector - - if items := src.Spec.NetworkPolicies.Items; len(items) > 0 { - in.Spec.NetworkPolicies.Items = items - } - - if items := src.Spec.LimitRanges.Items; len(items) > 0 { - in.Spec.LimitRanges.Items = items - } - - if scope := src.Spec.ResourceQuota.Scope; len(scope) > 0 { - in.Spec.ResourceQuota.Scope = ResourceQuotaScope(scope) - } - - if items := src.Spec.ResourceQuota.Items; len(items) > 0 { - in.Spec.ResourceQuota.Items = items - } - - in.Spec.AdditionalRoleBindings = make([]AdditionalRoleBindingsSpec, 0, len(src.Spec.AdditionalRoleBindings)) - for _, rb := range src.Spec.AdditionalRoleBindings { - in.Spec.AdditionalRoleBindings = append(in.Spec.AdditionalRoleBindings, AdditionalRoleBindingsSpec{ - ClusterRoleName: rb.ClusterRoleName, - Subjects: rb.Subjects, - }) - } - - in.Spec.ImagePullPolicies = make([]ImagePullPolicySpec, 0, len(src.Spec.ImagePullPolicies)) - for _, policy := range src.Spec.ImagePullPolicies { - in.Spec.ImagePullPolicies = append(in.Spec.ImagePullPolicies, ImagePullPolicySpec(policy)) - } - - if allowed := src.Spec.PriorityClasses; allowed != nil { - in.Spec.PriorityClasses = &AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - if v, found := annotations["capsule.clastix.io/cordon"]; found { - value, err := strconv.ParseBool(v) - if err == nil { - delete(annotations, "capsule.clastix.io/cordon") - } - - in.Spec.Cordoned = value - } - - if _, found := annotations[capsulev1beta1.ProtectedTenantAnnotation]; found { - in.Spec.PreventDeletion = true - - delete(annotations, capsulev1beta1.ProtectedTenantAnnotation) - } - - in.SetAnnotations(annotations) - - in.Status.Namespaces = src.Status.Namespaces - in.Status.Size = src.Status.Size - in.Status.State = tenantState(src.Status.State) - - return nil -} - -func (in *Tenant) ConvertTo(raw conversion.Hub) error { - dst, ok := raw.(*capsulev1beta1.Tenant) - if !ok { - return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) - } - - annotations := in.GetAnnotations() - if annotations == nil { - annotations = map[string]string{} - } - - dst.ObjectMeta = in.ObjectMeta - dst.Spec.Owners = make(capsulev1beta1.OwnerListSpec, 0, len(in.Spec.Owners)) - - for index, owner := range in.Spec.Owners { - proxySettings := make([]capsulev1beta1.ProxySettings, 0, len(owner.ProxyOperations)) - - for _, proxyOp := range owner.ProxyOperations { - ops := make([]capsulev1beta1.ProxyOperation, 0, len(proxyOp.Operations)) - - for _, op := range proxyOp.Operations { - ops = append(ops, capsulev1beta1.ProxyOperation(op)) - } - - proxySettings = append(proxySettings, capsulev1beta1.ProxySettings{ - Kind: capsulev1beta1.ProxyServiceKind(proxyOp.Kind), - Operations: ops, - }) - } - - dst.Spec.Owners = append(dst.Spec.Owners, capsulev1beta1.OwnerSpec{ - Kind: capsulev1beta1.OwnerKind(owner.Kind), - Name: owner.Name, - ProxyOperations: proxySettings, - }) - - if clusterRoles := owner.ClusterRoles; len(clusterRoles) > 0 { - annotations[fmt.Sprintf("%s/%d", capsulev1beta1.ClusterRoleNamesAnnotation, index)] = strings.Join(owner.ClusterRoles, ",") - } - } - - if nsOpts := in.Spec.NamespaceOptions; nsOpts != nil { - dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{} - - if quota := nsOpts.Quota; quota != nil { - dst.Spec.NamespaceOptions.Quota = quota - } - - if metadata := nsOpts.AdditionalMetadata; metadata != nil { - dst.Spec.NamespaceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{ - Labels: metadata.Labels, - Annotations: metadata.Annotations, - } - } - - if exact := nsOpts.ForbiddenAnnotations.Exact; len(exact) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",") - } - - if regex := nsOpts.ForbiddenAnnotations.Regex; len(regex) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex - } - - if exact := nsOpts.ForbiddenLabels.Exact; len(exact) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",") - } - - if regex := nsOpts.ForbiddenLabels.Regex; len(regex) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation] = regex - } - } - - if svcOpts := in.Spec.ServiceOptions; svcOpts != nil { - dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{} - - if metadata := svcOpts.AdditionalMetadata; metadata != nil { - dst.Spec.ServiceOptions.AdditionalMetadata = &capsulev1beta1.AdditionalMetadataSpec{ - Labels: metadata.Labels, - Annotations: metadata.Annotations, - } - } - - if allowed := svcOpts.AllowedServices; allowed != nil { - dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{ - NodePort: allowed.NodePort, - ExternalName: allowed.ExternalName, - LoadBalancer: allowed.ExternalName, - } - } - - if externalIPs := svcOpts.ExternalServiceIPs; externalIPs != nil { - allowed := make([]capsulev1beta1.AllowedIP, 0, len(externalIPs.Allowed)) - - for _, ip := range externalIPs.Allowed { - allowed = append(allowed, capsulev1beta1.AllowedIP(ip)) - } - - dst.Spec.ServiceOptions.ExternalServiceIPs = &capsulev1beta1.ExternalServiceIPsSpec{ - Allowed: allowed, - } - } - } - - if storageClass := in.Spec.StorageClasses; storageClass != nil { - dst.Spec.StorageClasses = &capsulev1beta1.AllowedListSpec{ - Exact: storageClass.Exact, - Regex: storageClass.Regex, - } - } - - dst.Spec.IngressOptions.HostnameCollisionScope = capsulev1beta1.HostnameCollisionScope(in.Spec.IngressOptions.HostnameCollisionScope) - - if allowed := in.Spec.IngressOptions.AllowedClasses; allowed != nil { - dst.Spec.IngressOptions.AllowedClasses = &capsulev1beta1.AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - if allowed := in.Spec.IngressOptions.AllowedHostnames; allowed != nil { - dst.Spec.IngressOptions.AllowedHostnames = &capsulev1beta1.AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - annotations[capsulev1beta1.DenyWildcard] = fmt.Sprintf("%t", in.Spec.IngressOptions.AllowWildcardHostnames) - - if allowed := in.Spec.ContainerRegistries; allowed != nil { - dst.Spec.ContainerRegistries = &capsulev1beta1.AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - dst.Spec.NodeSelector = in.Spec.NodeSelector - - dst.Spec.NetworkPolicies = capsulev1beta1.NetworkPolicySpec{ - Items: in.Spec.NetworkPolicies.Items, - } - - dst.Spec.LimitRanges = capsulev1beta1.LimitRangesSpec{ - Items: in.Spec.LimitRanges.Items, - } - - dst.Spec.ResourceQuota = capsulev1beta1.ResourceQuotaSpec{ - Scope: capsulev1beta1.ResourceQuotaScope(in.Spec.ResourceQuota.Scope), - Items: in.Spec.ResourceQuota.Items, - } - - dst.Spec.AdditionalRoleBindings = make([]capsulev1beta1.AdditionalRoleBindingsSpec, 0, len(in.Spec.AdditionalRoleBindings)) - for _, item := range in.Spec.AdditionalRoleBindings { - dst.Spec.AdditionalRoleBindings = append(dst.Spec.AdditionalRoleBindings, capsulev1beta1.AdditionalRoleBindingsSpec{ - ClusterRoleName: item.ClusterRoleName, - Subjects: item.Subjects, - }) - } - - dst.Spec.ImagePullPolicies = make([]capsulev1beta1.ImagePullPolicySpec, 0, len(in.Spec.ImagePullPolicies)) - for _, item := range in.Spec.ImagePullPolicies { - dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, capsulev1beta1.ImagePullPolicySpec(item)) - } - - if allowed := in.Spec.PriorityClasses; allowed != nil { - dst.Spec.PriorityClasses = &capsulev1beta1.AllowedListSpec{ - Exact: allowed.Exact, - Regex: allowed.Regex, - } - } - - dst.SetAnnotations(annotations) - - return nil -} diff --git a/api/v1beta2/ingress_options.go b/api/v1beta2/ingress_options.go index d55c0cfd..3d4cd718 100644 --- a/api/v1beta2/ingress_options.go +++ b/api/v1beta2/ingress_options.go @@ -3,9 +3,13 @@ package v1beta2 +import ( + "github.com/clastix/capsule/pkg/api" +) + type IngressOptions struct { // Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. - AllowedClasses *AllowedListSpec `json:"allowedClasses,omitempty"` + AllowedClasses *api.AllowedListSpec `json:"allowedClasses,omitempty"` // Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. // // @@ -18,9 +22,9 @@ type IngressOptions struct { // // Optional. // +kubebuilder:default=Disabled - HostnameCollisionScope HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"` + HostnameCollisionScope api.HostnameCollisionScope `json:"hostnameCollisionScope,omitempty"` // Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. - AllowedHostnames *AllowedListSpec `json:"allowedHostnames,omitempty"` + AllowedHostnames *api.AllowedListSpec `json:"allowedHostnames,omitempty"` // Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. AllowWildcardHostnames bool `json:"allowWildcardHostnames"` } diff --git a/api/v1beta2/namespace_options.go b/api/v1beta2/namespace_options.go index 10d7e906..a76b79a5 100644 --- a/api/v1beta2/namespace_options.go +++ b/api/v1beta2/namespace_options.go @@ -3,14 +3,18 @@ package v1beta2 +import ( + "github.com/clastix/capsule/pkg/api" +) + type NamespaceOptions struct { //+kubebuilder:validation:Minimum=1 // Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. Quota *int32 `json:"quota,omitempty"` // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. - AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` // Define the labels that a Tenant Owner cannot set for their Namespace resources. - ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels"` + ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels"` // Define the annotations that a Tenant Owner cannot set for their Namespace resources. - ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations"` + ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations"` } diff --git a/api/v1beta2/network_policy.go b/api/v1beta2/network_policy.go deleted file mode 100644 index 67668129..00000000 --- a/api/v1beta2/network_policy.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta2 - -import ( - networkingv1 "k8s.io/api/networking/v1" -) - -type NetworkPolicySpec struct { - Items []networkingv1.NetworkPolicySpec `json:"items,omitempty"` -} diff --git a/api/v1beta2/resource_quota.go b/api/v1beta2/resource_quota.go deleted file mode 100644 index 80534c91..00000000 --- a/api/v1beta2/resource_quota.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta2 - -import corev1 "k8s.io/api/core/v1" - -// +kubebuilder:validation:Enum=Tenant;Namespace -type ResourceQuotaScope string - -const ( - ResourceQuotaScopeTenant ResourceQuotaScope = "Tenant" - ResourceQuotaScopeNamespace ResourceQuotaScope = "Namespace" -) - -type ResourceQuotaSpec struct { - // +kubebuilder:default=Tenant - // Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant - Scope ResourceQuotaScope `json:"scope,omitempty"` - Items []corev1.ResourceQuotaSpec `json:"items,omitempty"` -} diff --git a/api/v1beta2/service_allowed_ips.go b/api/v1beta2/service_allowed_ips.go deleted file mode 100644 index 9eb6247e..00000000 --- a/api/v1beta2/service_allowed_ips.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta2 - -// +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" -type AllowedIP string - -type ExternalServiceIPsSpec struct { - Allowed []AllowedIP `json:"allowed"` -} diff --git a/api/v1beta2/tenant_annotations.go b/api/v1beta2/tenant_annotations.go deleted file mode 100644 index 79fde6b5..00000000 --- a/api/v1beta2/tenant_annotations.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta2 - -import ( - "fmt" - "strings" -) - -const ( - AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes" - AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp" - AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes" - AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp" - AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries" - AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp" -) - -func UsedQuotaFor(resource fmt.Stringer) string { - return "quota.capsule.clastix.io/used-" + strings.ReplaceAll(resource.String(), "/", "_") -} - -func HardQuotaFor(resource fmt.Stringer) string { - return "quota.capsule.clastix.io/hard-" + strings.ReplaceAll(resource.String(), "/", "_") -} diff --git a/api/v1beta2/tenant_conversion_hub.go b/api/v1beta2/tenant_conversion_hub.go new file mode 100644 index 00000000..de3dfb3a --- /dev/null +++ b/api/v1beta2/tenant_conversion_hub.go @@ -0,0 +1,246 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strconv" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" +) + +func (in *Tenant) ConvertFrom(raw conversion.Hub) error { + src, ok := raw.(*capsulev1beta1.Tenant) + if !ok { + return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) + } + + annotations := src.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + in.ObjectMeta = src.ObjectMeta + in.Spec.Owners = make(OwnerListSpec, 0, len(src.Spec.Owners)) + + for index, owner := range src.Spec.Owners { + proxySettings := make([]ProxySettings, 0, len(owner.ProxyOperations)) + + for _, proxyOp := range owner.ProxyOperations { + ops := make([]ProxyOperation, 0, len(proxyOp.Operations)) + + for _, op := range proxyOp.Operations { + ops = append(ops, ProxyOperation(op)) + } + + proxySettings = append(proxySettings, ProxySettings{ + Kind: ProxyServiceKind(proxyOp.Kind), + Operations: ops, + }) + } + + in.Spec.Owners = append(in.Spec.Owners, OwnerSpec{ + Kind: OwnerKind(owner.Kind), + Name: owner.Name, + ClusterRoles: owner.GetRoles(*src, index), + ProxyOperations: proxySettings, + }) + } + + if nsOpts := src.Spec.NamespaceOptions; nsOpts != nil { + in.Spec.NamespaceOptions = &NamespaceOptions{} + + in.Spec.NamespaceOptions.Quota = src.Spec.NamespaceOptions.Quota + + in.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenLabels.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenLabels.Regex = value + + delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenAnnotations.Exact = strings.Split(value, ",") + + delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation) + } + + if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found { + in.Spec.NamespaceOptions.ForbiddenAnnotations.Regex = value + + delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation) + } + } + + in.Spec.ServiceOptions = src.Spec.ServiceOptions + in.Spec.StorageClasses = src.Spec.StorageClasses + + if scope := src.Spec.IngressOptions.HostnameCollisionScope; len(scope) > 0 { + in.Spec.IngressOptions.HostnameCollisionScope = scope + } + + v, found := annotations[capsulev1beta1.DenyWildcard] + if found { + value, err := strconv.ParseBool(v) + if err == nil { + in.Spec.IngressOptions.AllowWildcardHostnames = !value + + delete(annotations, capsulev1beta1.DenyWildcard) + } + } + + if ingressClass := src.Spec.IngressOptions.AllowedClasses; ingressClass != nil { + in.Spec.IngressOptions.AllowedClasses = ingressClass + } + + if hostnames := src.Spec.IngressOptions.AllowedHostnames; hostnames != nil { + in.Spec.IngressOptions.AllowedHostnames = hostnames + } + + in.Spec.ContainerRegistries = src.Spec.ContainerRegistries + in.Spec.NodeSelector = src.Spec.NodeSelector + in.Spec.NetworkPolicies = src.Spec.NetworkPolicies + in.Spec.LimitRanges = src.Spec.LimitRanges + in.Spec.ResourceQuota = src.Spec.ResourceQuota + in.Spec.AdditionalRoleBindings = src.Spec.AdditionalRoleBindings + in.Spec.ImagePullPolicies = src.Spec.ImagePullPolicies + in.Spec.PriorityClasses = src.Spec.PriorityClasses + + if v, found := annotations["capsule.clastix.io/cordon"]; found { + value, err := strconv.ParseBool(v) + if err == nil { + delete(annotations, "capsule.clastix.io/cordon") + } + + in.Spec.Cordoned = value + } + + if _, found := annotations[capsulev1beta1.ProtectedTenantAnnotation]; found { + in.Spec.PreventDeletion = true + + delete(annotations, capsulev1beta1.ProtectedTenantAnnotation) + } + + in.SetAnnotations(annotations) + + in.Status.Namespaces = src.Status.Namespaces + in.Status.Size = src.Status.Size + in.Status.State = tenantState(src.Status.State) + + return nil +} + +func (in *Tenant) ConvertTo(raw conversion.Hub) error { + dst, ok := raw.(*capsulev1beta1.Tenant) + if !ok { + return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", raw) + } + + annotations := in.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + + dst.ObjectMeta = in.ObjectMeta + dst.Spec.Owners = make(capsulev1beta1.OwnerListSpec, 0, len(in.Spec.Owners)) + + for index, owner := range in.Spec.Owners { + proxySettings := make([]capsulev1beta1.ProxySettings, 0, len(owner.ProxyOperations)) + + for _, proxyOp := range owner.ProxyOperations { + ops := make([]capsulev1beta1.ProxyOperation, 0, len(proxyOp.Operations)) + + for _, op := range proxyOp.Operations { + ops = append(ops, capsulev1beta1.ProxyOperation(op)) + } + + proxySettings = append(proxySettings, capsulev1beta1.ProxySettings{ + Kind: capsulev1beta1.ProxyServiceKind(proxyOp.Kind), + Operations: ops, + }) + } + + dst.Spec.Owners = append(dst.Spec.Owners, capsulev1beta1.OwnerSpec{ + Kind: capsulev1beta1.OwnerKind(owner.Kind), + Name: owner.Name, + ProxyOperations: proxySettings, + }) + + if clusterRoles := owner.ClusterRoles; len(clusterRoles) > 0 { + annotations[fmt.Sprintf("%s/%d", capsulev1beta1.ClusterRoleNamesAnnotation, index)] = strings.Join(owner.ClusterRoles, ",") + } + } + + if nsOpts := in.Spec.NamespaceOptions; nsOpts != nil { + dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{} + + dst.Spec.NamespaceOptions.Quota = nsOpts.Quota + dst.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata + + if exact := nsOpts.ForbiddenAnnotations.Exact; len(exact) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",") + } + + if regex := nsOpts.ForbiddenAnnotations.Regex; len(regex) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex + } + + if exact := nsOpts.ForbiddenLabels.Exact; len(exact) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",") + } + + if regex := nsOpts.ForbiddenLabels.Regex; len(regex) > 0 { + annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation] = regex + } + } + + dst.Spec.ServiceOptions = in.Spec.ServiceOptions + dst.Spec.StorageClasses = in.Spec.StorageClasses + + dst.Spec.IngressOptions.HostnameCollisionScope = in.Spec.IngressOptions.HostnameCollisionScope + + if allowed := in.Spec.IngressOptions.AllowedClasses; allowed != nil { + dst.Spec.IngressOptions.AllowedClasses = allowed + } + + if allowed := in.Spec.IngressOptions.AllowedHostnames; allowed != nil { + dst.Spec.IngressOptions.AllowedHostnames = allowed + } + + annotations[capsulev1beta1.DenyWildcard] = fmt.Sprintf("%t", !in.Spec.IngressOptions.AllowWildcardHostnames) + + if allowed := in.Spec.ContainerRegistries; allowed != nil { + dst.Spec.ContainerRegistries = allowed + } + + dst.Spec.NodeSelector = in.Spec.NodeSelector + dst.Spec.NetworkPolicies = in.Spec.NetworkPolicies + dst.Spec.LimitRanges = in.Spec.LimitRanges + dst.Spec.ResourceQuota = in.Spec.ResourceQuota + dst.Spec.AdditionalRoleBindings = in.Spec.AdditionalRoleBindings + dst.Spec.ImagePullPolicies = in.Spec.ImagePullPolicies + dst.Spec.PriorityClasses = in.Spec.PriorityClasses + + if in.Spec.PreventDeletion { + annotations[capsulev1beta1.ProtectedTenantAnnotation] = "true" //nolint:goconst + } + + if in.Spec.Cordoned { + annotations["capsule.clastix.io/cordon"] = "true" + } + + dst.SetAnnotations(annotations) + + return nil +} diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index adc0c66b..d366eaaf 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -5,6 +5,8 @@ package v1beta2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/clastix/capsule/pkg/api" ) // TenantSpec defines the desired state of Tenant. @@ -14,27 +16,27 @@ type TenantSpec struct { // Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"` // Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. - ServiceOptions *ServiceOptions `json:"serviceOptions,omitempty"` + ServiceOptions *api.ServiceOptions `json:"serviceOptions,omitempty"` // Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. - StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"` + StorageClasses *api.AllowedListSpec `json:"storageClasses,omitempty"` // Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. IngressOptions IngressOptions `json:"ingressOptions,omitempty"` // Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. - ContainerRegistries *AllowedListSpec `json:"containerRegistries,omitempty"` + ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"` // Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. NodeSelector map[string]string `json:"nodeSelector,omitempty"` // Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. - NetworkPolicies NetworkPolicySpec `json:"networkPolicies,omitempty"` + NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitempty"` // Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. - LimitRanges LimitRangesSpec `json:"limitRanges,omitempty"` + LimitRanges api.LimitRangesSpec `json:"limitRanges,omitempty"` // Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. - ResourceQuota ResourceQuotaSpec `json:"resourceQuotas,omitempty"` + ResourceQuota api.ResourceQuotaSpec `json:"resourceQuotas,omitempty"` // Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. - AdditionalRoleBindings []AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` + AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` // Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. - ImagePullPolicies []ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` + ImagePullPolicies []api.ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` // Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. - PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"` + PriorityClasses *api.AllowedListSpec `json:"priorityClasses,omitempty"` // Toggling the Tenant resources cordoning, when enable resources cannot be deleted. Cordoned bool `json:"cordoned,omitempty"` // Prevent accidental deletion of the Tenant. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 71908929..4012bc96 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -9,8 +9,7 @@ package v1beta2 import ( - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" + "github.com/clastix/capsule/pkg/api" "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -64,56 +63,6 @@ func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) { - *out = *in - if in.Exact != nil { - in, out := &in.Exact, &out.Exact - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec. -func (in *AllowedListSpec) DeepCopy() *AllowedListSpec { - if in == nil { - return nil - } - out := new(AllowedListSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AllowedServices) DeepCopyInto(out *AllowedServices) { - *out = *in - if in.NodePort != nil { - in, out := &in.NodePort, &out.NodePort - *out = new(bool) - **out = **in - } - if in.ExternalName != nil { - in, out := &in.ExternalName, &out.ExternalName - *out = new(bool) - **out = **in - } - if in.LoadBalancer != nil { - in, out := &in.LoadBalancer, &out.LoadBalancer - *out = new(bool) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedServices. -func (in *AllowedServices) DeepCopy() *AllowedServices { - if in == nil { - return nil - } - out := new(AllowedServices) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in ByKindAndName) DeepCopyInto(out *ByKindAndName) { { @@ -234,57 +183,17 @@ func (in *CapsuleResources) DeepCopy() *CapsuleResources { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { - *out = *in - if in.Allowed != nil { - in, out := &in.Allowed, &out.Allowed - *out = make([]AllowedIP, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec. -func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec { - if in == nil { - return nil - } - out := new(ExternalServiceIPsSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ForbiddenListSpec) DeepCopyInto(out *ForbiddenListSpec) { - *out = *in - if in.Exact != nil { - in, out := &in.Exact, &out.Exact - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForbiddenListSpec. -func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec { - if in == nil { - return nil - } - out := new(ForbiddenListSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressOptions) DeepCopyInto(out *IngressOptions) { *out = *in if in.AllowedClasses != nil { in, out := &in.AllowedClasses, &out.AllowedClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.AllowedHostnames != nil { in, out := &in.AllowedHostnames, &out.AllowedHostnames - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } } @@ -299,28 +208,6 @@ func (in *IngressOptions) DeepCopy() *IngressOptions { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]corev1.LimitRangeSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangesSpec. -func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec { - if in == nil { - return nil - } - out := new(LimitRangesSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) { *out = *in @@ -331,7 +218,7 @@ func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) { } if in.AdditionalMetadata != nil { in, out := &in.AdditionalMetadata, &out.AdditionalMetadata - *out = new(AdditionalMetadataSpec) + *out = new(api.AdditionalMetadataSpec) (*in).DeepCopyInto(*out) } in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels) @@ -348,28 +235,6 @@ func (in *NamespaceOptions) DeepCopy() *NamespaceOptions { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]networkingv1.NetworkPolicySpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec. -func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { - if in == nil { - return nil - } - out := new(NetworkPolicySpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeMetadata) DeepCopyInto(out *NodeMetadata) { *out = *in @@ -470,58 +335,6 @@ func (in *ProxySettings) DeepCopy() *ProxySettings { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { - *out = *in - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]corev1.ResourceQuotaSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec. -func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { - if in == nil { - return nil - } - out := new(ResourceQuotaSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) { - *out = *in - if in.AdditionalMetadata != nil { - in, out := &in.AdditionalMetadata, &out.AdditionalMetadata - *out = new(AdditionalMetadataSpec) - (*in).DeepCopyInto(*out) - } - if in.AllowedServices != nil { - in, out := &in.AllowedServices, &out.AllowedServices - *out = new(AllowedServices) - (*in).DeepCopyInto(*out) - } - if in.ExternalServiceIPs != nil { - in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs - *out = new(ExternalServiceIPsSpec) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions. -func (in *ServiceOptions) DeepCopy() *ServiceOptions { - if in == nil { - return nil - } - out := new(ServiceOptions) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Tenant) DeepCopyInto(out *Tenant) { *out = *in @@ -598,18 +411,18 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.ServiceOptions != nil { in, out := &in.ServiceOptions, &out.ServiceOptions - *out = new(ServiceOptions) + *out = new(api.ServiceOptions) (*in).DeepCopyInto(*out) } if in.StorageClasses != nil { in, out := &in.StorageClasses, &out.StorageClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } in.IngressOptions.DeepCopyInto(&out.IngressOptions) if in.ContainerRegistries != nil { in, out := &in.ContainerRegistries, &out.ContainerRegistries - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } if in.NodeSelector != nil { @@ -624,19 +437,19 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { in.ResourceQuota.DeepCopyInto(&out.ResourceQuota) if in.AdditionalRoleBindings != nil { in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings - *out = make([]AdditionalRoleBindingsSpec, len(*in)) + *out = make([]api.AdditionalRoleBindingsSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.ImagePullPolicies != nil { in, out := &in.ImagePullPolicies, &out.ImagePullPolicies - *out = make([]ImagePullPolicySpec, len(*in)) + *out = make([]api.ImagePullPolicySpec, len(*in)) copy(*out, *in) } if in.PriorityClasses != nil { in, out := &in.PriorityClasses, &out.PriorityClasses - *out = new(AllowedListSpec) + *out = new(api.AllowedListSpec) (*in).DeepCopyInto(*out) } } diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index f171bd4b..5b36f41e 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -211,11 +211,11 @@ spec: type: integer namespacesMetadata: properties: - additionalAnnotations: + annotations: additionalProperties: type: string type: object - additionalLabels: + labels: additionalProperties: type: string type: object @@ -554,11 +554,11 @@ spec: type: array servicesMetadata: properties: - additionalAnnotations: + annotations: additionalProperties: type: string type: object - additionalLabels: + labels: additionalProperties: type: string type: object diff --git a/controllers/servicelabels/abstract.go b/controllers/servicelabels/abstract.go index 90b22884..54dd2dfb 100644 --- a/controllers/servicelabels/abstract.go +++ b/controllers/servicelabels/abstract.go @@ -21,6 +21,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/utils" ) type abstractServiceLabelsReconciler struct { @@ -77,7 +78,7 @@ func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespa return nil, err } - capsuleLabel, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) if _, ok := ns.GetLabels()[capsuleLabel]; !ok { return nil, NewNonTenantObject(namespacedName.Name) } diff --git a/api/v1alpha1/tenant_annotations.go b/controllers/tenant/annotations.go similarity index 69% rename from api/v1alpha1/tenant_annotations.go rename to controllers/tenant/annotations.go index bb3d4877..94711e92 100644 --- a/api/v1alpha1/tenant_annotations.go +++ b/controllers/tenant/annotations.go @@ -1,11 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1alpha1 - -import ( - "fmt" -) +package tenant const ( AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes" @@ -15,11 +11,3 @@ const ( AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries" AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp" ) - -func UsedQuotaFor(resource fmt.Stringer) string { - return "quota.capsule.clastix.io/used-" + resource.String() -} - -func HardQuotaFor(resource fmt.Stringer) string { - return "quota.capsule.clastix.io/hard-" + resource.String() -} diff --git a/controllers/tenant/limitranges.go b/controllers/tenant/limitranges.go index 8561c11f..4cd539d0 100644 --- a/controllers/tenant/limitranges.go +++ b/controllers/tenant/limitranges.go @@ -11,6 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/utils" ) // nolint:dupl @@ -40,11 +41,11 @@ func (r *Manager) syncLimitRange(ctx context.Context, tenant *capsulev1beta1.Ten // getting LimitRange labels for the mutateFn var tenantLabel, limitRangeLabel string - if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { return err } - if limitRangeLabel, err = capsulev1beta1.GetTypeLabel(&corev1.LimitRange{}); err != nil { + if limitRangeLabel, err = utils.GetTypeLabel(&corev1.LimitRange{}); err != nil { return err } diff --git a/controllers/tenant/namespaces.go b/controllers/tenant/namespaces.go index 73e2db38..53a9aca9 100644 --- a/controllers/tenant/namespaces.go +++ b/controllers/tenant/namespaces.go @@ -51,7 +51,7 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t return } - capsuleLabel, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) res, conflictErr = controllerutil.CreateOrUpdate(ctx, r.Client, ns, func() error { annotations := make(map[string]string) @@ -78,28 +78,28 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t if tnt.Spec.IngressOptions.AllowedClasses != nil { if len(tnt.Spec.IngressOptions.AllowedClasses.Exact) > 0 { - annotations[capsulev1beta1.AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressOptions.AllowedClasses.Exact, ",") + annotations[AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressOptions.AllowedClasses.Exact, ",") } if len(tnt.Spec.IngressOptions.AllowedClasses.Regex) > 0 { - annotations[capsulev1beta1.AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressOptions.AllowedClasses.Regex + annotations[AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressOptions.AllowedClasses.Regex } } if tnt.Spec.StorageClasses != nil { if len(tnt.Spec.StorageClasses.Exact) > 0 { - annotations[capsulev1beta1.AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",") + annotations[AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",") } if len(tnt.Spec.StorageClasses.Regex) > 0 { - annotations[capsulev1beta1.AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex + annotations[AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex } } if tnt.Spec.ContainerRegistries != nil { if len(tnt.Spec.ContainerRegistries.Exact) > 0 { - annotations[capsulev1beta1.AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",") + annotations[AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",") } if len(tnt.Spec.ContainerRegistries.Regex) > 0 { - annotations[capsulev1beta1.AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex + annotations[AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex } } diff --git a/controllers/tenant/networkpolicies.go b/controllers/tenant/networkpolicies.go index 34d35226..69f348bd 100644 --- a/controllers/tenant/networkpolicies.go +++ b/controllers/tenant/networkpolicies.go @@ -11,6 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/utils" ) // nolint:dupl @@ -43,11 +44,11 @@ func (r *Manager) syncNetworkPolicy(ctx context.Context, tenant *capsulev1beta1. // getting NetworkPolicy labels for the mutateFn var tenantLabel, networkPolicyLabel string - if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { return err } - if networkPolicyLabel, err = capsulev1beta1.GetTypeLabel(&networkingv1.NetworkPolicy{}); err != nil { + if networkPolicyLabel, err = utils.GetTypeLabel(&networkingv1.NetworkPolicy{}); err != nil { return err } diff --git a/controllers/tenant/resourcequotas.go b/controllers/tenant/resourcequotas.go index 88793529..ea0bd2e0 100644 --- a/controllers/tenant/resourcequotas.go +++ b/controllers/tenant/resourcequotas.go @@ -17,6 +17,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) // When the Resource Budget assigned to a Tenant is Tenant-scoped we have to rely on the ResourceQuota resources to @@ -36,15 +38,15 @@ func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta1 // getting ResourceQuota labels for the mutateFn var tenantLabel, typeLabel string - if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { return err } - if typeLabel, err = capsulev1beta1.GetTypeLabel(&corev1.ResourceQuota{}); err != nil { + if typeLabel, err = utils.GetTypeLabel(&corev1.ResourceQuota{}); err != nil { return err } // nolint:nestif - if tenant.Spec.ResourceQuota.Scope == capsulev1beta1.ResourceQuotaScopeTenant { + if tenant.Spec.ResourceQuota.Scope == api.ResourceQuotaScopeTenant { group := new(errgroup.Group) for i, q := range tenant.Spec.ResourceQuota.Items { @@ -157,11 +159,11 @@ func (r *Manager) syncResourceQuota(ctx context.Context, tenant *capsulev1beta1. // getting ResourceQuota labels for the mutateFn var tenantLabel, typeLabel string - if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { return err } - if typeLabel, err = capsulev1beta1.GetTypeLabel(&corev1.ResourceQuota{}); err != nil { + if typeLabel, err = utils.GetTypeLabel(&corev1.ResourceQuota{}); err != nil { return err } // Pruning resource of non-requested resources @@ -188,7 +190,7 @@ func (r *Manager) syncResourceQuota(ctx context.Context, tenant *capsulev1beta1. target.Spec.Scopes = resQuota.Scopes target.Spec.ScopeSelector = resQuota.ScopeSelector // In case of Namespace scope for the ResourceQuota we can easily apply the bare specification - if tenant.Spec.ResourceQuota.Scope == capsulev1beta1.ResourceQuotaScopeNamespace { + if tenant.Spec.ResourceQuota.Scope == api.ResourceQuotaScopeNamespace { target.Spec.Hard = resQuota.Hard } diff --git a/controllers/tenant/rolebindings.go b/controllers/tenant/rolebindings.go index 96682047..8b8f7cad 100644 --- a/controllers/tenant/rolebindings.go +++ b/controllers/tenant/rolebindings.go @@ -12,11 +12,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) // ownerClusterRoleBindings generates a Capsule AdditionalRoleBinding object for the Owner dynamic clusterrole in order // to take advantage of the additional role binding feature. -func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clusterRole string) capsulev1beta1.AdditionalRoleBindingsSpec { +func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clusterRole string) api.AdditionalRoleBindingsSpec { var subject rbacv1.Subject if owner.Kind == "ServiceAccount" { @@ -35,7 +37,7 @@ func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clust } } - return capsulev1beta1.AdditionalRoleBindingsSpec{ + return api.AdditionalRoleBindingsSpec{ ClusterRoleName: clusterRole, Subjects: []rbacv1.Subject{ subject, @@ -47,7 +49,7 @@ func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clust // applying Pod Security Policies or giving access to CRDs or specific API groups. func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta1.Tenant) (err error) { // hashing the RoleBinding name due to DNS RFC-1123 applied to Kubernetes labels - hashFn := func(binding capsulev1beta1.AdditionalRoleBindingsSpec) string { + hashFn := func(binding api.AdditionalRoleBindingsSpec) string { h := fnv.New64a() _, _ = h.Write([]byte(binding.ClusterRoleName)) @@ -86,14 +88,14 @@ func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta1.T return group.Wait() } -func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsulev1beta1.Tenant, ns string, keys []string, hashFn func(binding capsulev1beta1.AdditionalRoleBindingsSpec) string) (err error) { +func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsulev1beta1.Tenant, ns string, keys []string, hashFn func(binding api.AdditionalRoleBindingsSpec) string) (err error) { var tenantLabel, roleBindingLabel string - if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { return } - if roleBindingLabel, err = capsulev1beta1.GetTypeLabel(&rbacv1.RoleBinding{}); err != nil { + if roleBindingLabel, err = utils.GetTypeLabel(&rbacv1.RoleBinding{}); err != nil { return } @@ -101,7 +103,7 @@ func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsule return } - var roleBindings []capsulev1beta1.AdditionalRoleBindingsSpec + var roleBindings []api.AdditionalRoleBindingsSpec for index, owner := range tenant.Spec.Owners { for _, clusterRoleName := range owner.GetRoles(*tenant, index) { diff --git a/controllers/tenant/utils.go b/controllers/tenant/utils.go index 00b47518..f66d3038 100644 --- a/controllers/tenant/utils.go +++ b/controllers/tenant/utils.go @@ -11,7 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta1 "github.com/clastix/capsule/pkg/utils" ) // pruningResources is taking care of removing the no more requested sub-resources as LimitRange, ResourceQuota or diff --git a/e2e/additional_role_bindings_test.go b/e2e/additional_role_bindings_test.go index 29a7136d..ffda52fb 100644 --- a/e2e/additional_role_bindings_test.go +++ b/e2e/additional_role_bindings_test.go @@ -15,6 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a Namespace with an additional Role Binding", func() { @@ -29,7 +30,7 @@ var _ = Describe("creating a Namespace with an additional Role Binding", func() Kind: "User", }, }, - AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{ + AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{ { ClusterRoleName: "crds-rolebinding", Subjects: []rbacv1.Subject{ diff --git a/e2e/allowed_external_ips_test.go b/e2e/allowed_external_ips_test.go index f63c1d64..038c1b17 100644 --- a/e2e/allowed_external_ips_test.go +++ b/e2e/allowed_external_ips_test.go @@ -15,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing an allowed set of Service external IPs", func() { @@ -29,9 +30,9 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() { Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - ExternalServiceIPs: &capsulev1beta1.ExternalServiceIPsSpec{ - Allowed: []capsulev1beta1.AllowedIP{ + ServiceOptions: &api.ServiceOptions{ + ExternalServiceIPs: &api.ExternalServiceIPsSpec{ + Allowed: []api.AllowedIP{ "10.20.0.0/16", "192.168.1.2/32", }, diff --git a/e2e/container_registry_test.go b/e2e/container_registry_test.go index 7162daa7..ef854d06 100644 --- a/e2e/container_registry_test.go +++ b/e2e/container_registry_test.go @@ -15,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a Container Registry", func() { @@ -29,7 +30,7 @@ var _ = Describe("enforcing a Container Registry", func() { Kind: "User", }, }, - ContainerRegistries: &capsulev1beta1.AllowedListSpec{ + ContainerRegistries: &api.AllowedListSpec{ Exact: []string{"docker.io", "myregistry.azurecr.io"}, Regex: `quay\.\w+`, }, diff --git a/e2e/disable_externalname_test.go b/e2e/disable_externalname_test.go index 39f54b44..855455d4 100644 --- a/e2e/disable_externalname_test.go +++ b/e2e/disable_externalname_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating an ExternalName service when it is disabled for Tenant", func() { @@ -30,8 +31,8 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - AllowedServices: &capsulev1beta1.AllowedServices{ + ServiceOptions: &api.ServiceOptions{ + AllowedServices: &api.AllowedServices{ ExternalName: pointer.BoolPtr(false), }, }, diff --git a/e2e/disable_loadbalancer_test.go b/e2e/disable_loadbalancer_test.go index 76a050da..c532a180 100644 --- a/e2e/disable_loadbalancer_test.go +++ b/e2e/disable_loadbalancer_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant", func() { @@ -30,8 +31,8 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - AllowedServices: &capsulev1beta1.AllowedServices{ + ServiceOptions: &api.ServiceOptions{ + AllowedServices: &api.AllowedServices{ LoadBalancer: pointer.BoolPtr(false), }, }, diff --git a/e2e/disable_node_ports_test.go b/e2e/disable_node_ports_test.go index d2cd2be7..c384f453 100644 --- a/e2e/disable_node_ports_test.go +++ b/e2e/disable_node_ports_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a nodePort service when it is disabled for Tenant", func() { @@ -30,8 +31,8 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", f Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - AllowedServices: &capsulev1beta1.AllowedServices{ + ServiceOptions: &api.ServiceOptions{ + AllowedServices: &api.AllowedServices{ NodePort: pointer.BoolPtr(false), }, }, diff --git a/e2e/enable_loadbalancer_test.go b/e2e/enable_loadbalancer_test.go index a2262703..32a45e85 100644 --- a/e2e/enable_loadbalancer_test.go +++ b/e2e/enable_loadbalancer_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant", func() { @@ -30,8 +31,8 @@ var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant" Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - AllowedServices: &capsulev1beta1.AllowedServices{ + ServiceOptions: &api.ServiceOptions{ + AllowedServices: &api.AllowedServices{ LoadBalancer: pointer.BoolPtr(true), }, }, diff --git a/e2e/imagepullpolicy_multiple_test.go b/e2e/imagepullpolicy_multiple_test.go index af4528a4..f973e69c 100644 --- a/e2e/imagepullpolicy_multiple_test.go +++ b/e2e/imagepullpolicy_multiple_test.go @@ -14,6 +14,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing some defined ImagePullPolicy", func() { @@ -28,7 +29,7 @@ var _ = Describe("enforcing some defined ImagePullPolicy", func() { Kind: "User", }, }, - ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always", "IfNotPresent"}, + ImagePullPolicies: []api.ImagePullPolicySpec{"Always", "IfNotPresent"}, }, } diff --git a/e2e/imagepullpolicy_single_test.go b/e2e/imagepullpolicy_single_test.go index a93d9a05..41918ad0 100644 --- a/e2e/imagepullpolicy_single_test.go +++ b/e2e/imagepullpolicy_single_test.go @@ -14,6 +14,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a defined ImagePullPolicy", func() { @@ -28,7 +29,7 @@ var _ = Describe("enforcing a defined ImagePullPolicy", func() { Kind: "User", }, }, - ImagePullPolicies: []capsulev1beta1.ImagePullPolicySpec{"Always"}, + ImagePullPolicies: []api.ImagePullPolicySpec{"Always"}, }, } diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index b3775b00..f5f278b3 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", func() { @@ -34,7 +35,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }, }, IngressOptions: capsulev1beta1.IngressOptions{ - AllowedClasses: &capsulev1beta1.AllowedListSpec{ + AllowedClasses: &api.AllowedListSpec{ Exact: []string{ "nginx", "haproxy", diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index abc04a80..22459441 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -18,6 +18,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1", func() { @@ -33,7 +34,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" }, }, IngressOptions: capsulev1beta1.IngressOptions{ - AllowedClasses: &capsulev1beta1.AllowedListSpec{ + AllowedClasses: &api.AllowedListSpec{ Exact: []string{ "nginx", "haproxy", diff --git a/e2e/ingress_hostnames_collision_cluster_scope_test.go b/e2e/ingress_hostnames_collision_cluster_scope_test.go index 64204bcb..34ad3bb9 100644 --- a/e2e/ingress_hostnames_collision_cluster_scope_test.go +++ b/e2e/ingress_hostnames_collision_cluster_scope_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when handling Cluster scoped Ingress hostnames collision", func() { @@ -34,7 +35,7 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", fun }, }, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeCluster, + HostnameCollisionScope: api.HostnameCollisionScopeCluster, }, }, } @@ -50,7 +51,7 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", fun }, }, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeCluster, + HostnameCollisionScope: api.HostnameCollisionScopeCluster, }, }, } diff --git a/e2e/ingress_hostnames_collision_disabled_test.go b/e2e/ingress_hostnames_collision_disabled_test.go index b20f681e..f63cdc5a 100644 --- a/e2e/ingress_hostnames_collision_disabled_test.go +++ b/e2e/ingress_hostnames_collision_disabled_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when disabling Ingress hostnames collision", func() { @@ -34,7 +35,7 @@ var _ = Describe("when disabling Ingress hostnames collision", func() { }, }, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeDisabled, + HostnameCollisionScope: api.HostnameCollisionScopeDisabled, }, }, } diff --git a/e2e/ingress_hostnames_collision_namespace_scope_test.go b/e2e/ingress_hostnames_collision_namespace_scope_test.go index 6e7fe6a7..ecda0d38 100644 --- a/e2e/ingress_hostnames_collision_namespace_scope_test.go +++ b/e2e/ingress_hostnames_collision_namespace_scope_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when handling Namespace scoped Ingress hostnames collision", func() { @@ -34,7 +35,7 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", f }, }, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeNamespace, + HostnameCollisionScope: api.HostnameCollisionScopeNamespace, }, }, } diff --git a/e2e/ingress_hostnames_collision_tenant_scope_test.go b/e2e/ingress_hostnames_collision_tenant_scope_test.go index 4873f899..55385be1 100644 --- a/e2e/ingress_hostnames_collision_tenant_scope_test.go +++ b/e2e/ingress_hostnames_collision_tenant_scope_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func() { @@ -34,7 +35,7 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func }, }, IngressOptions: capsulev1beta1.IngressOptions{ - HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeTenant, + HostnameCollisionScope: api.HostnameCollisionScopeTenant, }, }, } diff --git a/e2e/ingress_hostnames_test.go b/e2e/ingress_hostnames_test.go index 2d27478d..d0f55640 100644 --- a/e2e/ingress_hostnames_test.go +++ b/e2e/ingress_hostnames_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Ingress hostnames", func() { @@ -34,7 +35,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }, }, IngressOptions: capsulev1beta1.IngressOptions{ - AllowedHostnames: &capsulev1beta1.AllowedListSpec{ + AllowedHostnames: &api.AllowedListSpec{ Exact: []string{"sigs.k8s.io", "operator.sdk", "domain.tld"}, Regex: `.*\.clastix\.io`, }, diff --git a/e2e/namespace_additional_metadata_test.go b/e2e/namespace_additional_metadata_test.go index 413178da..3bded7d2 100644 --- a/e2e/namespace_additional_metadata_test.go +++ b/e2e/namespace_additional_metadata_test.go @@ -14,6 +14,7 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a Namespace for a Tenant with additional metadata", func() { @@ -29,7 +30,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f }, }, NamespaceOptions: &capsulev1beta1.NamespaceOptions{ - AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{ + AdditionalMetadata: &api.AdditionalMetadataSpec{ Labels: map[string]string{ "k8s.io/custom-label": "foo", "clastix.io/custom-label": "bar", diff --git a/e2e/owner_webhooks_test.go b/e2e/owner_webhooks_test.go index 71ab1d81..1e9a4335 100644 --- a/e2e/owner_webhooks_test.go +++ b/e2e/owner_webhooks_test.go @@ -18,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant owner interacts with the webhooks", func() { @@ -32,13 +33,13 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { Kind: "User", }, }, - StorageClasses: &capsulev1beta1.AllowedListSpec{ + StorageClasses: &api.AllowedListSpec{ Exact: []string{ "cephfs", "glusterfs", }, }, - LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ + LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ { Limits: []corev1.LimitRangeItem{ { @@ -56,7 +57,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { }, }, }, - NetworkPolicies: capsulev1beta1.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ + NetworkPolicies: api.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ { Egress: []networkingv1.NetworkPolicyEgressRule{ { @@ -77,7 +78,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { }, }, }, - ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ + ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ { Hard: map[corev1.ResourceName]resource.Quantity{ corev1.ResourcePods: resource.MustParse("10"), diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index a8d1ac32..fc7dcdf4 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -15,6 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a Priority Class", func() { @@ -29,7 +30,7 @@ var _ = Describe("enforcing a Priority Class", func() { Kind: "User", }, }, - PriorityClasses: &capsulev1beta1.AllowedListSpec{ + PriorityClasses: &api.AllowedListSpec{ Exact: []string{"gold"}, Regex: "pc\\-\\w+", }, diff --git a/e2e/resource_quota_exceeded_test.go b/e2e/resource_quota_exceeded_test.go index fc8aa4df..d8edddbd 100644 --- a/e2e/resource_quota_exceeded_test.go +++ b/e2e/resource_quota_exceeded_test.go @@ -8,6 +8,7 @@ package e2e import ( "context" "fmt" + "github.com/clastix/capsule/pkg/api" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -33,7 +34,7 @@ var _ = Describe("exceeding a Tenant resource quota", func() { Kind: "User", }, }, - LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ + LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ { Limits: []corev1.LimitRangeItem{ { @@ -79,7 +80,7 @@ var _ = Describe("exceeding a Tenant resource quota", func() { }, }, }, - ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ + ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ { Hard: map[corev1.ResourceName]resource.Quantity{ corev1.ResourceLimitsCPU: resource.MustParse("8"), diff --git a/e2e/selecting_non_owned_tenant_test.go b/e2e/selecting_non_owned_tenant_test.go index 2aa62593..f1a47828 100644 --- a/e2e/selecting_non_owned_tenant_test.go +++ b/e2e/selecting_non_owned_tenant_test.go @@ -7,6 +7,7 @@ package e2e import ( "context" + "github.com/clastix/capsule/pkg/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -44,7 +45,7 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", func() var ns *corev1.Namespace By("assigning to the Namespace the Capsule Tenant label", func() { - l, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + l, err := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) Expect(err).ToNot(HaveOccurred()) ns := NewNamespace("tenant-non-owned-ns") diff --git a/e2e/selecting_tenant_with_label_test.go b/e2e/selecting_tenant_with_label_test.go index ed1459b2..abc359e0 100644 --- a/e2e/selecting_tenant_with_label_test.go +++ b/e2e/selecting_tenant_with_label_test.go @@ -7,6 +7,7 @@ package e2e import ( "context" + "github.com/clastix/capsule/pkg/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -59,7 +60,7 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi It("should be assigned to the selected Tenant", func() { ns := NewNamespace("tenant-2-ns") By("assigning to the Namespace the Capsule Tenant label", func() { - l, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + l, err := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) Expect(err).ToNot(HaveOccurred()) ns.Labels = map[string]string{ l: t2.Name, diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index 405b93b8..58fa24c8 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -24,6 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("adding metadata to Service objects", func() { @@ -38,8 +39,8 @@ var _ = Describe("adding metadata to Service objects", func() { Kind: "User", }, }, - ServiceOptions: &capsulev1beta1.ServiceOptions{ - AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{ + ServiceOptions: &api.ServiceOptions{ + AdditionalMetadata: &api.AdditionalMetadataSpec{ Labels: map[string]string{ "k8s.io/custom-label": "foo", "clastix.io/custom-label": "bar", diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go index 5f600677..8a25a0cc 100644 --- a/e2e/storage_class_test.go +++ b/e2e/storage_class_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/utils/pointer" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Storage classes", func() { @@ -30,7 +31,7 @@ var _ = Describe("when Tenant handles Storage classes", func() { Kind: "User", }, }, - StorageClasses: &capsulev1beta1.AllowedListSpec{ + StorageClasses: &api.AllowedListSpec{ Exact: []string{ "cephfs", "glusterfs", diff --git a/e2e/tenant_resources_changes_test.go b/e2e/tenant_resources_changes_test.go index eacbaf4c..5cd2d0dd 100644 --- a/e2e/tenant_resources_changes_test.go +++ b/e2e/tenant_resources_changes_test.go @@ -8,6 +8,7 @@ package e2e import ( "context" "fmt" + "github.com/clastix/capsule/pkg/api" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -33,7 +34,7 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() { Kind: "User", }, }, - LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ + LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ { Limits: []corev1.LimitRangeItem{ { @@ -79,7 +80,7 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() { }, }, }, - NetworkPolicies: capsulev1beta1.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ + NetworkPolicies: api.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ { Ingress: []networkingv1.NetworkPolicyIngressRule{ { @@ -127,7 +128,7 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() { NodeSelector: map[string]string{ "kubernetes.io/os": "linux", }, - ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ + ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ { Hard: map[corev1.ResourceName]resource.Quantity{ corev1.ResourceLimitsCPU: resource.MustParse("8"), diff --git a/e2e/tenant_resources_test.go b/e2e/tenant_resources_test.go index ca740e55..80742b8d 100644 --- a/e2e/tenant_resources_test.go +++ b/e2e/tenant_resources_test.go @@ -8,6 +8,7 @@ package e2e import ( "context" "fmt" + "github.com/clastix/capsule/pkg/api" "strings" . "github.com/onsi/ginkgo" @@ -33,7 +34,7 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() { Kind: "User", }, }, - LimitRanges: capsulev1beta1.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ + LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ { Limits: []corev1.LimitRangeItem{ { @@ -79,7 +80,7 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() { }, }, }, - NetworkPolicies: capsulev1beta1.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ + NetworkPolicies: api.NetworkPolicySpec{Items: []networkingv1.NetworkPolicySpec{ { Ingress: []networkingv1.NetworkPolicyIngressRule{ { @@ -127,7 +128,7 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() { NodeSelector: map[string]string{ "kubernetes.io/os": "linux", }, - ResourceQuota: capsulev1beta1.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ + ResourceQuota: api.ResourceQuotaSpec{Items: []corev1.ResourceQuotaSpec{ { Hard: map[corev1.ResourceName]resource.Quantity{ corev1.ResourceLimitsCPU: resource.MustParse("8"), diff --git a/api/v1beta1/additional_metadata.go b/pkg/api/additional_metadata.go similarity index 82% rename from api/v1beta1/additional_metadata.go rename to pkg/api/additional_metadata.go index 3cf7f5e1..7e8ef104 100644 --- a/api/v1beta1/additional_metadata.go +++ b/pkg/api/additional_metadata.go @@ -1,7 +1,9 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta1 +package api + +// +kubebuilder:object:generate=true type AdditionalMetadataSpec struct { Labels map[string]string `json:"labels,omitempty"` diff --git a/api/v1alpha1/additional_role_bindings.go b/pkg/api/additional_role_bindings.go similarity index 85% rename from api/v1alpha1/additional_role_bindings.go rename to pkg/api/additional_role_bindings.go index ecbe686b..e7c7e7f3 100644 --- a/api/v1alpha1/additional_role_bindings.go +++ b/pkg/api/additional_role_bindings.go @@ -1,10 +1,12 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1alpha1 +package api import rbacv1 "k8s.io/api/rbac/v1" +// +kubebuilder:object:generate=true + type AdditionalRoleBindingsSpec struct { ClusterRoleName string `json:"clusterRoleName"` // kubebuilder:validation:Minimum=1 diff --git a/api/v1beta2/allowed_list.go b/pkg/api/allowed_list.go similarity index 93% rename from api/v1beta2/allowed_list.go rename to pkg/api/allowed_list.go index 33e02c46..3bff873d 100644 --- a/api/v1beta2/allowed_list.go +++ b/pkg/api/allowed_list.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api import ( "regexp" @@ -9,6 +9,8 @@ import ( "strings" ) +// +kubebuilder:object:generate=true + type AllowedListSpec struct { Exact []string `json:"allowed,omitempty"` Regex string `json:"allowedRegex,omitempty"` diff --git a/api/v1beta1/allowed_list_test.go b/pkg/api/allowed_list_test.go similarity index 98% rename from api/v1beta1/allowed_list_test.go rename to pkg/api/allowed_list_test.go index 20364bf6..67fc247b 100644 --- a/api/v1beta1/allowed_list_test.go +++ b/pkg/api/allowed_list_test.go @@ -1,7 +1,8 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 + //nolint:dupl -package v1beta1 +package api import ( "testing" diff --git a/api/v1alpha1/external_service_ips.go b/pkg/api/external_service_ips.go similarity index 84% rename from api/v1alpha1/external_service_ips.go rename to pkg/api/external_service_ips.go index 2c1112d9..4bd1c9b6 100644 --- a/api/v1alpha1/external_service_ips.go +++ b/pkg/api/external_service_ips.go @@ -1,11 +1,13 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1alpha1 +package api // +kubebuilder:validation:Pattern="^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$" type AllowedIP string +// +kubebuilder:object:generate=true + type ExternalServiceIPsSpec struct { Allowed []AllowedIP `json:"allowed"` } diff --git a/api/v1beta2/forbidden_list.go b/pkg/api/forbidden_list.go similarity index 85% rename from api/v1beta2/forbidden_list.go rename to pkg/api/forbidden_list.go index 65c8e74e..acabb1ed 100644 --- a/api/v1beta2/forbidden_list.go +++ b/pkg/api/forbidden_list.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api import ( "regexp" @@ -9,12 +9,14 @@ import ( "strings" ) +// +kubebuilder:object:generate=true + type ForbiddenListSpec struct { Exact []string `json:"denied,omitempty"` Regex string `json:"deniedRegex,omitempty"` } -func (in *ForbiddenListSpec) ExactMatch(value string) (ok bool) { +func (in ForbiddenListSpec) ExactMatch(value string) (ok bool) { if len(in.Exact) > 0 { sort.SliceStable(in.Exact, func(i, j int) bool { return strings.ToLower(in.Exact[i]) < strings.ToLower(in.Exact[j]) diff --git a/api/v1beta2/forbidden_list_test.go b/pkg/api/forbidden_list_test.go similarity index 98% rename from api/v1beta2/forbidden_list_test.go rename to pkg/api/forbidden_list_test.go index 30bc9ac0..48c0e676 100644 --- a/api/v1beta2/forbidden_list_test.go +++ b/pkg/api/forbidden_list_test.go @@ -1,7 +1,8 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 +// //nolint:dupl -package v1beta2 +package api import ( "testing" diff --git a/api/v1beta2/hostname_collision_scope.go b/pkg/api/hostname_collision_scope.go similarity index 96% rename from api/v1beta2/hostname_collision_scope.go rename to pkg/api/hostname_collision_scope.go index a46ab934..31a09bde 100644 --- a/api/v1beta2/hostname_collision_scope.go +++ b/pkg/api/hostname_collision_scope.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api const ( HostnameCollisionScopeCluster HostnameCollisionScope = "Cluster" diff --git a/api/v1beta2/image_pull_policy.go b/pkg/api/image_pull_policy.go similarity index 93% rename from api/v1beta2/image_pull_policy.go rename to pkg/api/image_pull_policy.go index cb9e8e2f..3535f259 100644 --- a/api/v1beta2/image_pull_policy.go +++ b/pkg/api/image_pull_policy.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api // +kubebuilder:validation:Enum=Always;Never;IfNotPresent type ImagePullPolicySpec string diff --git a/api/v1beta2/limit_ranges.go b/pkg/api/limit_ranges.go similarity index 80% rename from api/v1beta2/limit_ranges.go rename to pkg/api/limit_ranges.go index cd5d0a13..649099ad 100644 --- a/api/v1beta2/limit_ranges.go +++ b/pkg/api/limit_ranges.go @@ -1,10 +1,12 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api import corev1 "k8s.io/api/core/v1" +// +kubebuilder:object:generate=true + type LimitRangesSpec struct { Items []corev1.LimitRangeSpec `json:"items,omitempty"` } diff --git a/api/v1beta1/network_policy.go b/pkg/api/network_policy.go similarity index 82% rename from api/v1beta1/network_policy.go rename to pkg/api/network_policy.go index 18c96489..b9789eb2 100644 --- a/api/v1beta1/network_policy.go +++ b/pkg/api/network_policy.go @@ -1,12 +1,14 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta1 +package api import ( networkingv1 "k8s.io/api/networking/v1" ) +// +kubebuilder:object:generate=true + type NetworkPolicySpec struct { Items []networkingv1.NetworkPolicySpec `json:"items,omitempty"` } diff --git a/api/v1beta1/resource_quota.go b/pkg/api/resource_quota.go similarity index 92% rename from api/v1beta1/resource_quota.go rename to pkg/api/resource_quota.go index 4f0a48a6..1eb5ae48 100644 --- a/api/v1beta1/resource_quota.go +++ b/pkg/api/resource_quota.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta1 +package api import corev1 "k8s.io/api/core/v1" @@ -13,6 +13,8 @@ const ( ResourceQuotaScopeNamespace ResourceQuotaScope = "Namespace" ) +// +kubebuilder:object:generate=true + type ResourceQuotaSpec struct { // +kubebuilder:default=Tenant // Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant diff --git a/api/v1beta2/service_allowed_types.go b/pkg/api/service_allowed_types.go similarity index 92% rename from api/v1beta2/service_allowed_types.go rename to pkg/api/service_allowed_types.go index 1f4abd4e..3f8369a4 100644 --- a/api/v1beta2/service_allowed_types.go +++ b/pkg/api/service_allowed_types.go @@ -1,7 +1,9 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api + +// +kubebuilder:object:generate=true type AllowedServices struct { //+kubebuilder:default=true diff --git a/api/v1beta2/service_options.go b/pkg/api/service_options.go similarity index 92% rename from api/v1beta2/service_options.go rename to pkg/api/service_options.go index a232aa5d..22168d4c 100644 --- a/api/v1beta2/service_options.go +++ b/pkg/api/service_options.go @@ -1,7 +1,9 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api + +// +kubebuilder:object:generate=true type ServiceOptions struct { // Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go new file mode 100644 index 00000000..139a7ec8 --- /dev/null +++ b/pkg/api/zz_generated.deepcopy.go @@ -0,0 +1,250 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by controller-gen. DO NOT EDIT. + +package api + +import ( + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/api/rbac/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec. +func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec { + if in == nil { + return nil + } + out := new(AdditionalMetadataSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) { + *out = *in + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]v1.Subject, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalRoleBindingsSpec. +func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { + if in == nil { + return nil + } + out := new(AdditionalRoleBindingsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllowedListSpec) DeepCopyInto(out *AllowedListSpec) { + *out = *in + if in.Exact != nil { + in, out := &in.Exact, &out.Exact + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedListSpec. +func (in *AllowedListSpec) DeepCopy() *AllowedListSpec { + if in == nil { + return nil + } + out := new(AllowedListSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllowedServices) DeepCopyInto(out *AllowedServices) { + *out = *in + if in.NodePort != nil { + in, out := &in.NodePort, &out.NodePort + *out = new(bool) + **out = **in + } + if in.ExternalName != nil { + in, out := &in.ExternalName, &out.ExternalName + *out = new(bool) + **out = **in + } + if in.LoadBalancer != nil { + in, out := &in.LoadBalancer, &out.LoadBalancer + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedServices. +func (in *AllowedServices) DeepCopy() *AllowedServices { + if in == nil { + return nil + } + out := new(AllowedServices) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { + *out = *in + if in.Allowed != nil { + in, out := &in.Allowed, &out.Allowed + *out = make([]AllowedIP, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalServiceIPsSpec. +func (in *ExternalServiceIPsSpec) DeepCopy() *ExternalServiceIPsSpec { + if in == nil { + return nil + } + out := new(ExternalServiceIPsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ForbiddenListSpec) DeepCopyInto(out *ForbiddenListSpec) { + *out = *in + if in.Exact != nil { + in, out := &in.Exact, &out.Exact + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForbiddenListSpec. +func (in *ForbiddenListSpec) DeepCopy() *ForbiddenListSpec { + if in == nil { + return nil + } + out := new(ForbiddenListSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LimitRangesSpec) DeepCopyInto(out *LimitRangesSpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]corev1.LimitRangeSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LimitRangesSpec. +func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec { + if in == nil { + return nil + } + out := new(LimitRangesSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]networkingv1.NetworkPolicySpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkPolicySpec. +func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { + if in == nil { + return nil + } + out := new(NetworkPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { + *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]corev1.ResourceQuotaSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuotaSpec. +func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { + if in == nil { + return nil + } + out := new(ResourceQuotaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) { + *out = *in + if in.AdditionalMetadata != nil { + in, out := &in.AdditionalMetadata, &out.AdditionalMetadata + *out = new(AdditionalMetadataSpec) + (*in).DeepCopyInto(*out) + } + if in.AllowedServices != nil { + in, out := &in.AllowedServices, &out.AllowedServices + *out = new(AllowedServices) + (*in).DeepCopyInto(*out) + } + if in.ExternalServiceIPs != nil { + in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs + *out = new(ExternalServiceIPsSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions. +func (in *ServiceOptions) DeepCopy() *ServiceOptions { + if in == nil { + return nil + } + out := new(ServiceOptions) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/configuration/client.go b/pkg/configuration/client.go index 3a0dd009..951bde20 100644 --- a/pkg/configuration/client.go +++ b/pkg/configuration/client.go @@ -15,7 +15,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta1 "github.com/clastix/capsule/pkg/api" ) // capsuleConfiguration is the Capsule Configuration retrieval mode diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 5036ac62..f36ec723 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -6,7 +6,7 @@ package configuration import ( "regexp" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta1 "github.com/clastix/capsule/pkg/api" ) const ( diff --git a/api/v1alpha1/tenant_labels.go b/pkg/utils/tenant_labels.go similarity index 79% rename from api/v1alpha1/tenant_labels.go rename to pkg/utils/tenant_labels.go index 7c49b68a..db97f7ae 100644 --- a/api/v1alpha1/tenant_labels.go +++ b/pkg/utils/tenant_labels.go @@ -1,7 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1alpha1 +package utils import ( "fmt" @@ -10,11 +10,15 @@ import ( networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" + + "github.com/clastix/capsule/api/v1alpha1" + "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/api/v1beta2" ) func GetTypeLabel(t runtime.Object) (label string, err error) { switch v := t.(type) { - case *Tenant: + case *v1alpha1.Tenant, *v1beta1.Tenant, *v1beta2.Tenant: return "capsule.clastix.io/tenant", nil case *corev1.LimitRange: return "capsule.clastix.io/limit-range", nil diff --git a/pkg/webhook/ingress/errors.go b/pkg/webhook/ingress/errors.go index cd0abc36..20988a52 100644 --- a/pkg/webhook/ingress/errors.go +++ b/pkg/webhook/ingress/errors.go @@ -7,15 +7,15 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) type ingressClassForbiddenError struct { className string - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewIngressClassForbidden(className string, spec capsulev1beta1.AllowedListSpec) error { +func NewIngressClassForbidden(className string, spec api.AllowedListSpec) error { return &ingressClassForbiddenError{ className: className, spec: spec, @@ -29,7 +29,7 @@ func (i ingressClassForbiddenError) Error() string { type ingressHostnameNotValidError struct { invalidHostnames []string notMatchingHostnames []string - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } type ingressHostnameCollisionError struct { @@ -44,7 +44,7 @@ func NewIngressHostnameCollision(hostname string) error { return &ingressHostnameCollisionError{hostname: hostname} } -func NewIngressHostnamesNotValid(invalidHostnames []string, notMatchingHostnames []string, spec capsulev1beta1.AllowedListSpec) error { +func NewIngressHostnamesNotValid(invalidHostnames []string, notMatchingHostnames []string, spec api.AllowedListSpec) error { return &ingressHostnameNotValidError{invalidHostnames: invalidHostnames, notMatchingHostnames: notMatchingHostnames, spec: spec} } @@ -54,10 +54,10 @@ func (i ingressHostnameNotValidError) Error() string { } type ingressClassNotValidError struct { - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewIngressClassNotValid(spec capsulev1beta1.AllowedListSpec) error { +func NewIngressClassNotValid(spec api.AllowedListSpec) error { return &ingressClassNotValidError{ spec: spec, } @@ -68,7 +68,7 @@ func (i ingressClassNotValidError) Error() string { } // nolint:predeclared -func appendClassError(spec capsulev1beta1.AllowedListSpec) (append string) { +func appendClassError(spec api.AllowedListSpec) (append string) { if len(spec.Exact) > 0 { append += fmt.Sprintf(", one of the following (%s)", strings.Join(spec.Exact, ", ")) } @@ -81,7 +81,7 @@ func appendClassError(spec capsulev1beta1.AllowedListSpec) (append string) { } // nolint:predeclared -func appendHostnameError(spec capsulev1beta1.AllowedListSpec) (append string) { +func appendHostnameError(spec api.AllowedListSpec) (append string) { if len(spec.Exact) > 0 { append = fmt.Sprintf(", specify one of the following (%s)", strings.Join(spec.Exact, ", ")) } diff --git a/pkg/webhook/ingress/validate_collision.go b/pkg/webhook/ingress/validate_collision.go index 62b2a33d..f7c508d4 100644 --- a/pkg/webhook/ingress/validate_collision.go +++ b/pkg/webhook/ingress/validate_collision.go @@ -19,6 +19,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" "github.com/clastix/capsule/pkg/configuration" "github.com/clastix/capsule/pkg/indexer/ingress" capsulewebhook "github.com/clastix/capsule/pkg/webhook" @@ -48,7 +49,7 @@ func (r *collision) OnCreate(client client.Client, decoder *admission.Decoder, r return utils.ErroredResponse(err) } - if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == capsulev1beta1.HostnameCollisionScopeDisabled { + if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == api.HostnameCollisionScopeDisabled { return nil } @@ -83,7 +84,7 @@ func (r *collision) OnUpdate(client client.Client, decoder *admission.Decoder, r return utils.ErroredResponse(err) } - if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == capsulev1beta1.HostnameCollisionScopeDisabled { + if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == api.HostnameCollisionScopeDisabled { return nil } @@ -110,7 +111,7 @@ func (r *collision) OnDelete(client.Client, *admission.Decoder, record.EventReco } // nolint:gocognit,gocyclo,cyclop -func (r *collision) validateCollision(ctx context.Context, clt client.Client, ing Ingress, scope capsulev1beta1.HostnameCollisionScope) error { +func (r *collision) validateCollision(ctx context.Context, clt client.Client, ing Ingress, scope api.HostnameCollisionScope) error { for hostname, paths := range ing.HostnamePathsPairs() { for path := range paths { var ingressObjList client.ObjectList @@ -127,7 +128,7 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in namespaces := sets.NewString() // nolint:exhaustive switch scope { - case capsulev1beta1.HostnameCollisionScopeCluster: + case api.HostnameCollisionScopeCluster: tenantList := &capsulev1beta1.TenantList{} if err := clt.List(ctx, tenantList); err != nil { return err @@ -136,7 +137,7 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in for _, tenant := range tenantList.Items { namespaces.Insert(tenant.Status.Namespaces...) } - case capsulev1beta1.HostnameCollisionScopeTenant: + case api.HostnameCollisionScopeTenant: selector := client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", ing.Namespace())} tenantList := &capsulev1beta1.TenantList{} @@ -147,7 +148,7 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in for _, tenant := range tenantList.Items { namespaces.Insert(tenant.Status.Namespaces...) } - case capsulev1beta1.HostnameCollisionScopeNamespace: + case api.HostnameCollisionScopeNamespace: namespaces.Insert(ing.Namespace()) } diff --git a/pkg/webhook/namespace/errors.go b/pkg/webhook/namespace/errors.go index 6cdf732f..04e87804 100644 --- a/pkg/webhook/namespace/errors.go +++ b/pkg/webhook/namespace/errors.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta1 "github.com/clastix/capsule/pkg/api" ) // nolint:predeclared diff --git a/pkg/webhook/namespace/patch.go b/pkg/webhook/namespace/patch.go index 13a289e5..23dcaeb6 100644 --- a/pkg/webhook/namespace/patch.go +++ b/pkg/webhook/namespace/patch.go @@ -15,6 +15,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + utils2 "github.com/clastix/capsule/pkg/utils" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -46,7 +47,7 @@ func (r *patchHandler) OnUpdate(c client.Client, decoder *admission.Decoder, rec } // Get Tenant Label - ln, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + ln, err := utils2.GetTypeLabel(&capsulev1beta1.Tenant{}) if err != nil { response := admission.Errored(http.StatusBadRequest, err) diff --git a/pkg/webhook/networkpolicy/validating.go b/pkg/webhook/networkpolicy/validating.go index 0ac22113..35d01507 100644 --- a/pkg/webhook/networkpolicy/validating.go +++ b/pkg/webhook/networkpolicy/validating.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + utils2 "github.com/clastix/capsule/pkg/utils" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -40,7 +41,7 @@ func (r *handler) generic(ctx context.Context, req admission.Request, client cli tnt := &capsulev1beta1.Tenant{} - l, _ := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + l, _ := utils2.GetTypeLabel(&capsulev1beta1.Tenant{}) if v, ok := np.GetLabels()[l]; ok { if err = client.Get(ctx, types.NamespacedName{Name: v}, tnt); err != nil { return nil, err diff --git a/pkg/webhook/node/errors.go b/pkg/webhook/node/errors.go index 355c74d0..e8435a78 100644 --- a/pkg/webhook/node/errors.go +++ b/pkg/webhook/node/errors.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta1 "github.com/clastix/capsule/pkg/api" ) // nolint:predeclared diff --git a/pkg/webhook/ownerreference/patching.go b/pkg/webhook/ownerreference/patching.go index df7f963a..2a39aba7 100644 --- a/pkg/webhook/ownerreference/patching.go +++ b/pkg/webhook/ownerreference/patching.go @@ -21,6 +21,7 @@ import ( capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" "github.com/clastix/capsule/pkg/configuration" + utils2 "github.com/clastix/capsule/pkg/utils" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -61,7 +62,7 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client return &response } - ln, err := capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}) + ln, err := utils2.GetTypeLabel(&capsulev1beta1.Tenant{}) if err != nil { response := admission.Errored(http.StatusBadRequest, err) diff --git a/pkg/webhook/pod/containerregistry_errors.go b/pkg/webhook/pod/containerregistry_errors.go index 76454d52..776594a4 100644 --- a/pkg/webhook/pod/containerregistry_errors.go +++ b/pkg/webhook/pod/containerregistry_errors.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) type missingContainerRegistryError struct { @@ -24,10 +24,10 @@ func NewMissingContainerRegistryError(image string) error { type registryClassForbiddenError struct { fqci string - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewContainerRegistryForbidden(image string, spec capsulev1beta1.AllowedListSpec) error { +func NewContainerRegistryForbidden(image string, spec api.AllowedListSpec) error { return ®istryClassForbiddenError{ fqci: image, spec: spec, diff --git a/pkg/webhook/pod/priorityclass_errors.go b/pkg/webhook/pod/priorityclass_errors.go index ba7c9919..21b91d33 100644 --- a/pkg/webhook/pod/priorityclass_errors.go +++ b/pkg/webhook/pod/priorityclass_errors.go @@ -7,15 +7,15 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) type podPriorityClassForbiddenError struct { priorityClassName string - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewPodPriorityClassForbidden(priorityClassName string, spec capsulev1beta1.AllowedListSpec) error { +func NewPodPriorityClassForbidden(priorityClassName string, spec api.AllowedListSpec) error { return &podPriorityClassForbiddenError{ priorityClassName: priorityClassName, spec: spec, diff --git a/pkg/webhook/pvc/errors.go b/pkg/webhook/pvc/errors.go index 2774f835..196f6b06 100644 --- a/pkg/webhook/pvc/errors.go +++ b/pkg/webhook/pvc/errors.go @@ -7,21 +7,21 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) type storageClassNotValidError struct { - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewStorageClassNotValid(storageClasses capsulev1beta1.AllowedListSpec) error { +func NewStorageClassNotValid(storageClasses api.AllowedListSpec) error { return &storageClassNotValidError{ spec: storageClasses, } } // nolint:predeclared -func appendError(spec capsulev1beta1.AllowedListSpec) (append string) { +func appendError(spec api.AllowedListSpec) (append string) { if len(spec.Exact) > 0 { append += fmt.Sprintf(", one of the following (%s)", strings.Join(spec.Exact, ", ")) } @@ -39,10 +39,10 @@ func (s storageClassNotValidError) Error() (err string) { type storageClassForbiddenError struct { className string - spec capsulev1beta1.AllowedListSpec + spec api.AllowedListSpec } -func NewStorageClassForbidden(className string, storageClasses capsulev1beta1.AllowedListSpec) error { +func NewStorageClassForbidden(className string, storageClasses api.AllowedListSpec) error { return &storageClassForbiddenError{ className: className, spec: storageClasses, diff --git a/pkg/webhook/service/errors.go b/pkg/webhook/service/errors.go index 6802fda8..c85cb65b 100644 --- a/pkg/webhook/service/errors.go +++ b/pkg/webhook/service/errors.go @@ -7,14 +7,14 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) type externalServiceIPForbiddenError struct { cidr []string } -func NewExternalServiceIPForbidden(allowedIps []capsulev1beta1.AllowedIP) error { +func NewExternalServiceIPForbidden(allowedIps []api.AllowedIP) error { cidr := make([]string, 0, len(allowedIps)) for _, i := range allowedIps { From abbb281d45139c1859739c4cf43a5e8cda44ad1f Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:45:06 +0200 Subject: [PATCH 018/153] chore(lint): adding goheader rule and removing unused ones --- .golangci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 7f62eb65..b611560a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,6 +15,11 @@ linters-settings: - standard - default - prefix(github.com/clastix/capsule) + goheader: + template: |- + Copyright 2020-2021 Clastix Labs + SPDX-License-Identifier: Apache-2.0 + linters: enable-all: true disable: @@ -35,10 +40,6 @@ linters: - varnamelen - wrapcheck -issues: - exclude: - - Using the variable on range scope .* in function literal - service: golangci-lint-version: 1.45.2 From 4835b948394bf149aef7dca5be7298fa22755d55 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:47:06 +0200 Subject: [PATCH 019/153] style: conforming go files headers --- api/v1alpha1/capsuleconfiguration_annotations.go | 3 +++ api/v1beta1/namespace_options.go | 3 +++ api/v1beta1/owner_list.go | 3 +++ api/v1beta1/owner_list_test.go | 3 +++ controllers/tenant/limitranges.go | 3 +++ controllers/tenant/manager.go | 3 +++ controllers/tenant/networkpolicies.go | 3 +++ controllers/tenant/resourcequotas.go | 3 +++ controllers/tenant/resourcequotas_quota.go | 3 +++ controllers/tenant/rolebindings.go | 3 +++ controllers/tenant/utils.go | 3 +++ controllers/utils/name_matching.go | 3 +++ pkg/webhook/ingress/validate_wildcard.go | 3 +++ pkg/webhook/route/ownerreference.go | 3 +++ pkg/webhook/tenant/protected.go | 2 +- pkg/webhook/utils/is_capsule_user.go | 3 +++ pkg/webhook/utils/is_tenant_owner.go | 3 +++ 17 files changed, 49 insertions(+), 1 deletion(-) diff --git a/api/v1alpha1/capsuleconfiguration_annotations.go b/api/v1alpha1/capsuleconfiguration_annotations.go index 83ce062e..9fb42c54 100644 --- a/api/v1alpha1/capsuleconfiguration_annotations.go +++ b/api/v1alpha1/capsuleconfiguration_annotations.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package v1alpha1 const ( diff --git a/api/v1beta1/namespace_options.go b/api/v1beta1/namespace_options.go index 4135c4d8..88b45a28 100644 --- a/api/v1beta1/namespace_options.go +++ b/api/v1beta1/namespace_options.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package v1beta1 import ( diff --git a/api/v1beta1/owner_list.go b/api/v1beta1/owner_list.go index dd0c4209..b3f9d728 100644 --- a/api/v1beta1/owner_list.go +++ b/api/v1beta1/owner_list.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package v1beta1 import ( diff --git a/api/v1beta1/owner_list_test.go b/api/v1beta1/owner_list_test.go index 6877478a..c8b62a03 100644 --- a/api/v1beta1/owner_list_test.go +++ b/api/v1beta1/owner_list_test.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package v1beta1 import ( diff --git a/controllers/tenant/limitranges.go b/controllers/tenant/limitranges.go index 4cd539d0..59e4e152 100644 --- a/controllers/tenant/limitranges.go +++ b/controllers/tenant/limitranges.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/manager.go b/controllers/tenant/manager.go index fe42c948..1ea17a12 100644 --- a/controllers/tenant/manager.go +++ b/controllers/tenant/manager.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/networkpolicies.go b/controllers/tenant/networkpolicies.go index 69f348bd..90f0379d 100644 --- a/controllers/tenant/networkpolicies.go +++ b/controllers/tenant/networkpolicies.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/resourcequotas.go b/controllers/tenant/resourcequotas.go index ea0bd2e0..348877fc 100644 --- a/controllers/tenant/resourcequotas.go +++ b/controllers/tenant/resourcequotas.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/resourcequotas_quota.go b/controllers/tenant/resourcequotas_quota.go index e0f4151d..c20be18f 100644 --- a/controllers/tenant/resourcequotas_quota.go +++ b/controllers/tenant/resourcequotas_quota.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/rolebindings.go b/controllers/tenant/rolebindings.go index 8b8f7cad..a1d0126d 100644 --- a/controllers/tenant/rolebindings.go +++ b/controllers/tenant/rolebindings.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/tenant/utils.go b/controllers/tenant/utils.go index f66d3038..bca8b446 100644 --- a/controllers/tenant/utils.go +++ b/controllers/tenant/utils.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package tenant import ( diff --git a/controllers/utils/name_matching.go b/controllers/utils/name_matching.go index 7b1c540c..ddc3dbdf 100644 --- a/controllers/utils/name_matching.go +++ b/controllers/utils/name_matching.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package utils import ( diff --git a/pkg/webhook/ingress/validate_wildcard.go b/pkg/webhook/ingress/validate_wildcard.go index 62f9e214..a865ae77 100644 --- a/pkg/webhook/ingress/validate_wildcard.go +++ b/pkg/webhook/ingress/validate_wildcard.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package ingress import ( diff --git a/pkg/webhook/route/ownerreference.go b/pkg/webhook/route/ownerreference.go index d4e3a098..9fb903a2 100644 --- a/pkg/webhook/route/ownerreference.go +++ b/pkg/webhook/route/ownerreference.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package route import ( diff --git a/pkg/webhook/tenant/protected.go b/pkg/webhook/tenant/protected.go index e5381bfa..b5a5a18d 100644 --- a/pkg/webhook/tenant/protected.go +++ b/pkg/webhook/tenant/protected.go @@ -1,4 +1,4 @@ -// Copyright 2020-2022 Clastix Labs +// Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 package tenant diff --git a/pkg/webhook/utils/is_capsule_user.go b/pkg/webhook/utils/is_capsule_user.go index 5c163073..fd933ea4 100644 --- a/pkg/webhook/utils/is_capsule_user.go +++ b/pkg/webhook/utils/is_capsule_user.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package utils import ( diff --git a/pkg/webhook/utils/is_tenant_owner.go b/pkg/webhook/utils/is_tenant_owner.go index 34207f9f..0d6f39b3 100644 --- a/pkg/webhook/utils/is_tenant_owner.go +++ b/pkg/webhook/utils/is_tenant_owner.go @@ -1,3 +1,6 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + package utils import ( From 360a8d2b5620f4cd3d384fe9d99f310c9645645e Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:48:38 +0200 Subject: [PATCH 020/153] refactor: using interfaces for accessing tenant namespaces --- api/v1beta1/tenant_types.go | 10 ++++++++++ api/v1beta2/tenant_types.go | 10 ++++++++++ pkg/api/status_namespaces.go | 8 ++++++++ 3 files changed, 28 insertions(+) create mode 100644 pkg/api/status_namespaces.go diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 9e17b53e..009a6f1a 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -72,3 +72,13 @@ type TenantList struct { func init() { SchemeBuilder.Register(&Tenant{}, &TenantList{}) } + +func (in *Tenant) GetNamespaces() (res []string) { + res = make([]string, 0, len(in.Status.Namespaces)) + + for _, ns := range in.Status.Namespaces { + res = append(res, ns) + } + + return +} diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index d366eaaf..04f8fa2c 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -62,6 +62,16 @@ type Tenant struct { Status TenantStatus `json:"status,omitempty"` } +func (in *Tenant) GetNamespaces() (res []string) { + res = make([]string, 0, len(in.Status.Namespaces)) + + for _, ns := range in.Status.Namespaces { + res = append(res, ns) + } + + return +} + //+kubebuilder:object:root=true // TenantList contains a list of Tenant. diff --git a/pkg/api/status_namespaces.go b/pkg/api/status_namespaces.go new file mode 100644 index 00000000..016ff841 --- /dev/null +++ b/pkg/api/status_namespaces.go @@ -0,0 +1,8 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package api + +type Tenant interface { + GetNamespaces() []string +} From cade41da810d61f5060ee04e5fb39e755fbba8ad Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:51:43 +0200 Subject: [PATCH 021/153] feat(api): globaltenantresource and tenantresource support --- PROJECT | 20 +- api/v1beta1/groupversion_info.go | 4 +- api/v1beta2/groupversion_info.go | 4 +- api/v1beta2/tenant_labels.go | 4 +- api/v1beta2/tenantresource_global.go | 49 ++++ api/v1beta2/tenantresource_namespaced.go | 75 ++++++ api/v1beta2/tenantresource_types.go | 72 ++++++ api/v1beta2/zz_generated.deepcopy.go | 312 +++++++++++++++++++++++ 8 files changed, 529 insertions(+), 11 deletions(-) create mode 100644 api/v1beta2/tenantresource_global.go create mode 100644 api/v1beta2/tenantresource_namespaced.go create mode 100644 api/v1beta2/tenantresource_types.go diff --git a/PROJECT b/PROJECT index 72aada3c..ff22733d 100644 --- a/PROJECT +++ b/PROJECT @@ -9,7 +9,6 @@ repo: github.com/clastix/capsule resources: - api: crdVersion: v1 - namespaced: false controller: true domain: clastix.io group: capsule @@ -21,7 +20,6 @@ resources: webhookVersion: v1 - api: crdVersion: v1 - namespaced: false controller: true domain: clastix.io group: capsule @@ -30,7 +28,6 @@ resources: version: v1alpha1 - api: crdVersion: v1 - namespaced: false domain: clastix.io group: capsule kind: Tenant @@ -38,7 +35,6 @@ resources: version: v1beta1 - api: crdVersion: v1 - namespaced: false domain: clastix.io group: capsule kind: Tenant @@ -46,11 +42,25 @@ resources: version: v1beta2 - api: crdVersion: v1 - namespaced: false controller: true domain: clastix.io group: capsule kind: CapsuleConfiguration path: github.com/clastix/capsule/api/v1beta2 version: v1beta2 +- api: + crdVersion: v1 + namespaced: true + domain: clastix.io + group: capsule + kind: TenantResource + path: github.com/clastix/capsule/api/v1beta2 + version: v1beta2 +- api: + crdVersion: v1 + domain: clastix.io + group: capsule + kind: GlobalTenantResource + path: github.com/clastix/capsule/api/v1beta2 + version: v1beta2 version: "3" diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go index 341689ec..4a99993c 100644 --- a/api/v1beta1/groupversion_info.go +++ b/api/v1beta1/groupversion_info.go @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // Package v1beta1 contains API Schema definitions for the capsule v1beta1 API group -//+kubebuilder:object:generate=true -//+groupName=capsule.clastix.io +// +kubebuilder:object:generate=true +// +groupName=capsule.clastix.io package v1beta1 import ( diff --git a/api/v1beta2/groupversion_info.go b/api/v1beta2/groupversion_info.go index 3117d80f..37920ac4 100644 --- a/api/v1beta2/groupversion_info.go +++ b/api/v1beta2/groupversion_info.go @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // Package v1beta2 contains API Schema definitions for the capsule v1beta2 API group -//+kubebuilder:object:generate=true -//+groupName=capsule.clastix.io +// +kubebuilder:object:generate=true +// +groupName=capsule.clastix.io package v1beta2 import ( diff --git a/api/v1beta2/tenant_labels.go b/api/v1beta2/tenant_labels.go index 98d0e064..7bd2a922 100644 --- a/api/v1beta2/tenant_labels.go +++ b/api/v1beta2/tenant_labels.go @@ -9,10 +9,10 @@ import ( corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/runtime" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func GetTypeLabel(t runtime.Object) (label string, err error) { +func GetTypeLabel(t metav1.Object) (label string, err error) { switch v := t.(type) { case *Tenant: return "capsule.clastix.io/tenant", nil diff --git a/api/v1beta2/tenantresource_global.go b/api/v1beta2/tenantresource_global.go new file mode 100644 index 00000000..537b2caa --- /dev/null +++ b/api/v1beta2/tenantresource_global.go @@ -0,0 +1,49 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. +type GlobalTenantResourceSpec struct { + // Defines the Tenant selector used target the tenants on which resources must be propagated. + TenantSelector metav1.LabelSelector `json:"tenantSelector,omitempty"` + TenantResourceSpec `json:",inline"` +} + +// GlobalTenantResourceStatus defines the observed state of GlobalTenantResource. +type GlobalTenantResourceStatus struct { + // List of Tenants addressed by the GlobalTenantResource. + SelectedTenants []string `json:"selectedTenants"` + // List of the replicated resources for the given TenantResource. + ProcessedItems []ObjectReferenceStatus `json:"processedItems"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster + +// GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. +type GlobalTenantResource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GlobalTenantResourceSpec `json:"spec,omitempty"` + Status GlobalTenantResourceStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// GlobalTenantResourceList contains a list of GlobalTenantResource. +type GlobalTenantResourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GlobalTenantResource `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GlobalTenantResource{}, &GlobalTenantResourceList{}) +} diff --git a/api/v1beta2/tenantresource_namespaced.go b/api/v1beta2/tenantresource_namespaced.go new file mode 100644 index 00000000..d0852613 --- /dev/null +++ b/api/v1beta2/tenantresource_namespaced.go @@ -0,0 +1,75 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// TenantResourceSpec defines the desired state of TenantResource. +type TenantResourceSpec struct { + // Define the period of time upon a second reconciliation must be invoked. + // Keep in mind that any change to the manifests will trigger a new reconciliation. + // +kubebuilder:default="60s" + ResyncPeriod metav1.Duration `json:"resyncPeriod"` + // When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. + // Disable this to keep replicated resources although the deletion of the replication manifest. + // +kubebuilder:default=true + PruningOnDelete *bool `json:"pruningOnDelete,omitempty"` + // Defines the rules to select targeting Namespace, along with the objects that must be replicated. + Resources []ResourceSpec `json:"resources"` +} + +type ResourceSpec struct { + // Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. + // In case of nil value, all the Tenant Namespaces are targeted. + NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"` + // List of the resources already existing in other Namespaces that must be replicated. + NamespacedItems []ObjectReference `json:"namespacedItems,omitempty"` + // List of raw resources that must be replicated. + RawItems []RawExtension `json:"rawItems,omitempty"` + // Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be + // added to the replicated resources. + AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` +} + +// +kubebuilder:validation:XEmbeddedResource +// +kubebuilder:validation:XPreserveUnknownFields +type RawExtension struct { + runtime.RawExtension `json:",inline"` +} + +// TenantResourceStatus defines the observed state of TenantResource. +type TenantResourceStatus struct { + // List of the replicated resources for the given TenantResource. + ProcessedItems []ObjectReferenceStatus `json:"processedItems"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. +// The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. +// For such cases, the GlobalTenantResource must be used. +type TenantResource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TenantResourceSpec `json:"spec,omitempty"` + Status TenantResourceStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// TenantResourceList contains a list of TenantResource. +type TenantResourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TenantResource `json:"items"` +} + +func init() { + SchemeBuilder.Register(&TenantResource{}, &TenantResourceList{}) +} diff --git a/api/v1beta2/tenantresource_types.go b/api/v1beta2/tenantresource_types.go new file mode 100644 index 00000000..8f701b22 --- /dev/null +++ b/api/v1beta2/tenantresource_types.go @@ -0,0 +1,72 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type ObjectReferenceAbstract struct { + // Kind of the referent. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind"` + // Namespace of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + Namespace string `json:"namespace"` + // API version of the referent. + APIVersion string `json:"apiVersion,omitempty"` +} + +type ObjectReferenceStatus struct { + ObjectReferenceAbstract `json:",inline"` + // Name of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Name string `json:"name"` +} + +type ObjectReference struct { + ObjectReferenceAbstract `json:",inline"` + // Label selector used to select the given resources in the given Namespace. + Selector metav1.LabelSelector `json:"selector"` +} + +func (in *ObjectReferenceStatus) String() string { + return fmt.Sprintf("Kind=%s,APIVersion=%s,Namespace=%s,Name=%s", in.Kind, in.APIVersion, in.Namespace, in.Name) +} + +func (in *ObjectReferenceStatus) ParseFromString(value string) error { + rawParts := strings.Split(value, ",") + + if len(rawParts) != 4 { + return fmt.Errorf("unexpected raw parts") + } + + for _, i := range rawParts { + parts := strings.Split(i, "=") + + if len(parts) != 2 { + return fmt.Errorf("unrecognized separator") + } + + k, v := parts[0], parts[1] + + switch k { + case "Kind": + in.Kind = v + case "APIVersion": + in.APIVersion = v + case "Namespace": + in.Namespace = v + case "Name": + in.Name = v + default: + return fmt.Errorf("unrecognized marker: %s", k) + } + } + + return nil +} diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 4012bc96..f0994bb1 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -11,6 +11,7 @@ package v1beta2 import ( "github.com/clastix/capsule/pkg/api" "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -183,6 +184,107 @@ func (in *CapsuleResources) DeepCopy() *CapsuleResources { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalTenantResource) DeepCopyInto(out *GlobalTenantResource) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResource. +func (in *GlobalTenantResource) DeepCopy() *GlobalTenantResource { + if in == nil { + return nil + } + out := new(GlobalTenantResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GlobalTenantResource) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalTenantResourceList) DeepCopyInto(out *GlobalTenantResourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GlobalTenantResource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResourceList. +func (in *GlobalTenantResourceList) DeepCopy() *GlobalTenantResourceList { + if in == nil { + return nil + } + out := new(GlobalTenantResourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GlobalTenantResourceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalTenantResourceSpec) DeepCopyInto(out *GlobalTenantResourceSpec) { + *out = *in + in.TenantSelector.DeepCopyInto(&out.TenantSelector) + in.TenantResourceSpec.DeepCopyInto(&out.TenantResourceSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResourceSpec. +func (in *GlobalTenantResourceSpec) DeepCopy() *GlobalTenantResourceSpec { + if in == nil { + return nil + } + out := new(GlobalTenantResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalTenantResourceStatus) DeepCopyInto(out *GlobalTenantResourceStatus) { + *out = *in + if in.SelectedTenants != nil { + in, out := &in.SelectedTenants, &out.SelectedTenants + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ProcessedItems != nil { + in, out := &in.ProcessedItems, &out.ProcessedItems + *out = make([]ObjectReferenceStatus, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalTenantResourceStatus. +func (in *GlobalTenantResourceStatus) DeepCopy() *GlobalTenantResourceStatus { + if in == nil { + return nil + } + out := new(GlobalTenantResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressOptions) DeepCopyInto(out *IngressOptions) { *out = *in @@ -267,6 +369,54 @@ func (in *NonLimitedResourceError) DeepCopy() *NonLimitedResourceError { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectReference) DeepCopyInto(out *ObjectReference) { + *out = *in + out.ObjectReferenceAbstract = in.ObjectReferenceAbstract + in.Selector.DeepCopyInto(&out.Selector) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference. +func (in *ObjectReference) DeepCopy() *ObjectReference { + if in == nil { + return nil + } + out := new(ObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectReferenceAbstract) DeepCopyInto(out *ObjectReferenceAbstract) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReferenceAbstract. +func (in *ObjectReferenceAbstract) DeepCopy() *ObjectReferenceAbstract { + if in == nil { + return nil + } + out := new(ObjectReferenceAbstract) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectReferenceStatus) DeepCopyInto(out *ObjectReferenceStatus) { + *out = *in + out.ObjectReferenceAbstract = in.ObjectReferenceAbstract +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReferenceStatus. +func (in *ObjectReferenceStatus) DeepCopy() *ObjectReferenceStatus { + if in == nil { + return nil + } + out := new(ObjectReferenceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in OwnerListSpec) DeepCopyInto(out *OwnerListSpec) { { @@ -335,6 +485,61 @@ func (in *ProxySettings) DeepCopy() *ProxySettings { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RawExtension) DeepCopyInto(out *RawExtension) { + *out = *in + in.RawExtension.DeepCopyInto(&out.RawExtension) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RawExtension. +func (in *RawExtension) DeepCopy() *RawExtension { + if in == nil { + return nil + } + out := new(RawExtension) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) { + *out = *in + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.NamespacedItems != nil { + in, out := &in.NamespacedItems, &out.NamespacedItems + *out = make([]ObjectReference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.RawItems != nil { + in, out := &in.RawItems, &out.RawItems + *out = make([]RawExtension, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AdditionalMetadata != nil { + in, out := &in.AdditionalMetadata, &out.AdditionalMetadata + *out = new(AdditionalMetadataSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSpec. +func (in *ResourceSpec) DeepCopy() *ResourceSpec { + if in == nil { + return nil + } + out := new(ResourceSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Tenant) DeepCopyInto(out *Tenant) { *out = *in @@ -394,6 +599,113 @@ func (in *TenantList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantResource) DeepCopyInto(out *TenantResource) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResource. +func (in *TenantResource) DeepCopy() *TenantResource { + if in == nil { + return nil + } + out := new(TenantResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TenantResource) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantResourceList) DeepCopyInto(out *TenantResourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TenantResource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResourceList. +func (in *TenantResourceList) DeepCopy() *TenantResourceList { + if in == nil { + return nil + } + out := new(TenantResourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TenantResourceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantResourceSpec) DeepCopyInto(out *TenantResourceSpec) { + *out = *in + out.ResyncPeriod = in.ResyncPeriod + if in.PruningOnDelete != nil { + in, out := &in.PruningOnDelete, &out.PruningOnDelete + *out = new(bool) + **out = **in + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]ResourceSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResourceSpec. +func (in *TenantResourceSpec) DeepCopy() *TenantResourceSpec { + if in == nil { + return nil + } + out := new(TenantResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantResourceStatus) DeepCopyInto(out *TenantResourceStatus) { + *out = *in + if in.ProcessedItems != nil { + in, out := &in.ProcessedItems, &out.ProcessedItems + *out = make([]ObjectReferenceStatus, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantResourceStatus. +func (in *TenantResourceStatus) DeepCopy() *TenantResourceStatus { + if in == nil { + return nil + } + out := new(TenantResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { *out = *in From b7038d00a2531d3f2ae54d5bd62a3ba0a1da81aa Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 28 Oct 2022 18:24:24 -0400 Subject: [PATCH 022/153] chore(kustomize): globaltenantresource and tenantresource support --- ...sule.clastix.io_globaltenantresources.yaml | 283 ++++++++++++ .../capsule.clastix.io_tenantresources.yaml | 232 ++++++++++ config/crd/kustomization.yaml | 2 + .../cainjection_in_globaltenantresources.yaml | 7 + .../cainjection_in_tenantresources.yaml | 7 + .../webhook_in_globaltenantresources.yaml | 16 + .../patches/webhook_in_tenantresources.yaml | 16 + config/install.yaml | 405 ++++++++++++++++++ .../globaltenantresource_editor_role.yaml | 24 ++ .../globaltenantresource_viewer_role.yaml | 20 + config/rbac/tenantresource_editor_role.yaml | 24 ++ config/rbac/tenantresource_viewer_role.yaml | 20 + .../capsule_v1beta2_globaltenantresource.yaml | 39 ++ .../capsule_v1beta2_tenantresource.yaml | 36 ++ 14 files changed, 1131 insertions(+) create mode 100644 config/crd/bases/capsule.clastix.io_globaltenantresources.yaml create mode 100644 config/crd/bases/capsule.clastix.io_tenantresources.yaml create mode 100644 config/crd/patches/cainjection_in_globaltenantresources.yaml create mode 100644 config/crd/patches/cainjection_in_tenantresources.yaml create mode 100644 config/crd/patches/webhook_in_globaltenantresources.yaml create mode 100644 config/crd/patches/webhook_in_tenantresources.yaml create mode 100644 config/rbac/globaltenantresource_editor_role.yaml create mode 100644 config/rbac/globaltenantresource_viewer_role.yaml create mode 100644 config/rbac/tenantresource_editor_role.yaml create mode 100644 config/rbac/tenantresource_viewer_role.yaml create mode 100644 config/samples/capsule_v1beta2_globaltenantresource.yaml create mode 100644 config/samples/capsule_v1beta2_tenantresource.yaml diff --git a/config/crd/bases/capsule.clastix.io_globaltenantresources.yaml b/config/crd/bases/capsule.clastix.io_globaltenantresources.yaml new file mode 100644 index 00000000..c6b3869a --- /dev/null +++ b/config/crd/bases/capsule.clastix.io_globaltenantresources.yaml @@ -0,0 +1,283 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: globaltenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: GlobalTenantResource + listKind: GlobalTenantResourceList + plural: globaltenantresources + singular: globaltenantresource + scope: Cluster + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: GlobalTenantResource allows to propagate resource replications + to a specific subset of Tenant resources. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all + the objects replicated so far will be automatically deleted. Disable + this to keep replicated resources although the deletion of the replication + manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along + with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource + controller, defines additional metadata that must be added + to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant + Namespaces on which the resources must be propagated. In case + of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. This + array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other + Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources + in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values + array must be non-empty. If the operator is + Exists or DoesNotExist, the values array must + be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation + must be invoked. Keep in mind that any change to the manifests will + trigger a new reconciliation. + type: string + tenantSelector: + description: Defines the Tenant selector used target the tenants on + which resources must be propagated. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - resources + - resyncPeriod + type: object + status: + description: GlobalTenantResourceStatus defines the observed state of + GlobalTenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + selectedTenants: + description: List of Tenants addressed by the GlobalTenantResource. + items: + type: string + type: array + required: + - processedItems + - selectedTenants + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/capsule.clastix.io_tenantresources.yaml b/config/crd/bases/capsule.clastix.io_tenantresources.yaml new file mode 100644 index 00000000..bd2a3681 --- /dev/null +++ b/config/crd/bases/capsule.clastix.io_tenantresources.yaml @@ -0,0 +1,232 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: tenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: TenantResource + listKind: TenantResourceList + plural: tenantresources + singular: tenantresource + scope: Namespaced + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: TenantResource allows a Tenant Owner, if enabled with proper + RBAC, to propagate resources in its Namespace. The object must be deployed + in a Tenant Namespace, and cannot reference object living in non-Tenant + namespaces. For such cases, the GlobalTenantResource must be used. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantResourceSpec defines the desired state of TenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all + the objects replicated so far will be automatically deleted. Disable + this to keep replicated resources although the deletion of the replication + manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along + with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource + controller, defines additional metadata that must be added + to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant + Namespaces on which the resources must be propagated. In case + of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. This + array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other + Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources + in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values + array must be non-empty. If the operator is + Exists or DoesNotExist, the values array must + be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation + must be invoked. Keep in mind that any change to the manifests will + trigger a new reconciliation. + type: string + required: + - resources + - resyncPeriod + type: object + status: + description: TenantResourceStatus defines the observed state of TenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + required: + - processedItems + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 4c753b21..07d5edd3 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,6 +4,8 @@ resources: - bases/capsule.clastix.io_tenants.yaml - bases/capsule.clastix.io_capsuleconfigurations.yaml +- bases/capsule.clastix.io_tenantresources.yaml +- bases/capsule.clastix.io_globaltenantresources.yaml # +kubebuilder:scaffold:crdkustomizeresource # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_globaltenantresources.yaml b/config/crd/patches/cainjection_in_globaltenantresources.yaml new file mode 100644 index 00000000..fdf9c307 --- /dev/null +++ b/config/crd/patches/cainjection_in_globaltenantresources.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: globaltenantresources.capsule.clastix.io diff --git a/config/crd/patches/cainjection_in_tenantresources.yaml b/config/crd/patches/cainjection_in_tenantresources.yaml new file mode 100644 index 00000000..e7158ad9 --- /dev/null +++ b/config/crd/patches/cainjection_in_tenantresources.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: tenantresources.capsule.clastix.io diff --git a/config/crd/patches/webhook_in_globaltenantresources.yaml b/config/crd/patches/webhook_in_globaltenantresources.yaml new file mode 100644 index 00000000..12cfc50a --- /dev/null +++ b/config/crd/patches/webhook_in_globaltenantresources.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: globaltenantresources.capsule.clastix.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_tenantresources.yaml b/config/crd/patches/webhook_in_tenantresources.yaml new file mode 100644 index 00000000..827ccf34 --- /dev/null +++ b/config/crd/patches/webhook_in_tenantresources.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tenantresources.capsule.clastix.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/install.yaml b/config/install.yaml index cef1ef3c..632a7601 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -158,6 +158,411 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: globaltenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: GlobalTenantResource + listKind: GlobalTenantResourceList + plural: globaltenantresources + singular: globaltenantresource + scope: Cluster + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation. + type: string + tenantSelector: + description: Defines the Tenant selector used target the tenants on which resources must be propagated. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - resources + - resyncPeriod + type: object + status: + description: GlobalTenantResourceStatus defines the observed state of GlobalTenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + selectedTenants: + description: List of Tenants addressed by the GlobalTenantResource. + items: + type: string + type: array + required: + - processedItems + - selectedTenants + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: tenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: TenantResource + listKind: TenantResourceList + plural: tenantresources + singular: tenantresource + scope: Namespaced + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. For such cases, the GlobalTenantResource must be used. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantResourceSpec defines the desired state of TenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation. + type: string + required: + - resources + - resyncPeriod + type: object + status: + description: TenantResourceStatus defines the observed state of TenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + required: + - processedItems + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.10.0 diff --git a/config/rbac/globaltenantresource_editor_role.yaml b/config/rbac/globaltenantresource_editor_role.yaml new file mode 100644 index 00000000..60d87ab1 --- /dev/null +++ b/config/rbac/globaltenantresource_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit globaltenantresources. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: globaltenantresource-editor-role +rules: +- apiGroups: + - capsule.clastix.io + resources: + - globaltenantresources + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - capsule.clastix.io + resources: + - globaltenantresources/status + verbs: + - get diff --git a/config/rbac/globaltenantresource_viewer_role.yaml b/config/rbac/globaltenantresource_viewer_role.yaml new file mode 100644 index 00000000..d535a89b --- /dev/null +++ b/config/rbac/globaltenantresource_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view globaltenantresources. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: globaltenantresource-viewer-role +rules: +- apiGroups: + - capsule.clastix.io + resources: + - globaltenantresources + verbs: + - get + - list + - watch +- apiGroups: + - capsule.clastix.io + resources: + - globaltenantresources/status + verbs: + - get diff --git a/config/rbac/tenantresource_editor_role.yaml b/config/rbac/tenantresource_editor_role.yaml new file mode 100644 index 00000000..2812649e --- /dev/null +++ b/config/rbac/tenantresource_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit tenantresources. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: tenantresource-editor-role +rules: +- apiGroups: + - capsule.clastix.io + resources: + - tenantresources + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - capsule.clastix.io + resources: + - tenantresources/status + verbs: + - get diff --git a/config/rbac/tenantresource_viewer_role.yaml b/config/rbac/tenantresource_viewer_role.yaml new file mode 100644 index 00000000..b37ea627 --- /dev/null +++ b/config/rbac/tenantresource_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view tenantresources. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: tenantresource-viewer-role +rules: +- apiGroups: + - capsule.clastix.io + resources: + - tenantresources + verbs: + - get + - list + - watch +- apiGroups: + - capsule.clastix.io + resources: + - tenantresources/status + verbs: + - get diff --git a/config/samples/capsule_v1beta2_globaltenantresource.yaml b/config/samples/capsule_v1beta2_globaltenantresource.yaml new file mode 100644 index 00000000..3d2853ce --- /dev/null +++ b/config/samples/capsule_v1beta2_globaltenantresource.yaml @@ -0,0 +1,39 @@ +apiVersion: capsule.clastix.io/v1beta2 +kind: GlobalTenantResource +metadata: + name: green-production +spec: + tenantSelector: + matchLabels: + energy: green + resyncPeriod: 60s + pruningOnDelete: true + resources: + - namespaceSelector: + matchLabels: + environment: production + additionalMetadata: + labels: + labels.energy.io: green + annotations: + annotations.energy.io: green + namespacedItems: + - apiVersion: v1 + kind: Secret + namespace: default + selector: + matchLabels: + replicate: green + rawItems: + - apiVersion: v1 + kind: Secret + metadata: + name: raw-secret-1 + - apiVersion: v1 + kind: Secret + metadata: + name: raw-secret-2 + - apiVersion: v1 + kind: Secret + metadata: + name: raw-secret-3 \ No newline at end of file diff --git a/config/samples/capsule_v1beta2_tenantresource.yaml b/config/samples/capsule_v1beta2_tenantresource.yaml new file mode 100644 index 00000000..db255f5d --- /dev/null +++ b/config/samples/capsule_v1beta2_tenantresource.yaml @@ -0,0 +1,36 @@ +apiVersion: capsule.clastix.io/v1beta2 +kind: TenantResource +metadata: + name: wind-objects +spec: + resyncPeriod: 60s + pruningOnDelete: true + resources: + - namespaceSelector: + matchLabels: + environment: production + additionalMetadata: + labels: + labels.energy.io: wind + annotations: + annotations.energy.io: wind + namespacedItems: + - apiVersion: v1 + kind: Secret + namespace: wind-production + selector: + matchLabels: + replicate: solar + rawItems: + - apiVersion: v1 + kind: Secret + metadata: + name: wind-secret-1 + - apiVersion: v1 + kind: Secret + metadata: + name: wind-secret-2 + - apiVersion: v1 + kind: Secret + metadata: + name: wind-secret-3 \ No newline at end of file From d01c9035ac6de76bb33580a0ddb26256f4acbdc6 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:54:38 +0200 Subject: [PATCH 023/153] chore(helm)!: globaltenantresource and tenantresource support --- .../crds/globaltenantresources-crd.yaml | 222 ++++++++++++++++++ charts/capsule/crds/tenantresources-crd.yaml | 185 +++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 charts/capsule/crds/globaltenantresources-crd.yaml create mode 100644 charts/capsule/crds/tenantresources-crd.yaml diff --git a/charts/capsule/crds/globaltenantresources-crd.yaml b/charts/capsule/crds/globaltenantresources-crd.yaml new file mode 100644 index 00000000..5519a4b6 --- /dev/null +++ b/charts/capsule/crds/globaltenantresources-crd.yaml @@ -0,0 +1,222 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: globaltenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: GlobalTenantResource + listKind: GlobalTenantResourceList + plural: globaltenantresources + singular: globaltenantresource + scope: Cluster + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation. + type: string + tenantSelector: + description: Defines the Tenant selector used target the tenants on which resources must be propagated. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - resources + - resyncPeriod + type: object + status: + description: GlobalTenantResourceStatus defines the observed state of GlobalTenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + selectedTenants: + description: List of Tenants addressed by the GlobalTenantResource. + items: + type: string + type: array + required: + - processedItems + - selectedTenants + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/capsule/crds/tenantresources-crd.yaml b/charts/capsule/crds/tenantresources-crd.yaml new file mode 100644 index 00000000..c1d2a4c7 --- /dev/null +++ b/charts/capsule/crds/tenantresources-crd.yaml @@ -0,0 +1,185 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: tenantresources.capsule.clastix.io +spec: + group: capsule.clastix.io + names: + kind: TenantResource + listKind: TenantResourceList + plural: tenantresources + singular: tenantresource + scope: Namespaced + versions: + - name: v1beta2 + schema: + openAPIV3Schema: + description: TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. For such cases, the GlobalTenantResource must be used. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantResourceSpec defines the desired state of TenantResource. + properties: + pruningOnDelete: + default: true + description: When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest. + type: boolean + resources: + description: Defines the rules to select targeting Namespace, along with the objects that must be replicated. + items: + properties: + additionalMetadata: + description: Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + description: Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespacedItems: + description: List of the resources already existing in other Namespaces that must be replicated. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + selector: + description: Label selector used to select the given resources in the given Namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - kind + - namespace + - selector + type: object + type: array + rawItems: + description: List of raw resources that must be replicated. + items: + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + type: array + type: object + type: array + resyncPeriod: + default: 60s + description: Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation. + type: string + required: + - resources + - resyncPeriod + type: object + status: + description: TenantResourceStatus defines the observed state of TenantResource. + properties: + processedItems: + description: List of the replicated resources for the given TenantResource. + items: + properties: + apiVersion: + description: API version of the referent. + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - kind + - name + - namespace + type: object + type: array + required: + - processedItems + type: object + type: object + served: true + storage: true + subresources: + status: {} From 503e3fc1d04ae503b098d9ed5d6af657d0a22cc1 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:55:21 +0200 Subject: [PATCH 024/153] feat: globaltenantresource and tenantresource reconciliation --- api/v1beta2/additional_metadata.go | 9 - api/v1beta2/tenantresource_namespaced.go | 4 +- api/v1beta2/zz_generated.deepcopy.go | 31 +-- controllers/resources/global.go | 186 ++++++++++++++++ controllers/resources/namespaced.go | 144 ++++++++++++ controllers/resources/processor.go | 17 ++ controllers/resources/processor_finalizer.go | 54 +++++ controllers/resources/processor_pruning.go | 67 ++++++ controllers/resources/processor_section.go | 223 +++++++++++++++++++ go.sum | 1 - main.go | 11 + pkg/indexer/indexer.go | 5 +- pkg/indexer/tenant/namespaces.go | 16 +- 13 files changed, 716 insertions(+), 52 deletions(-) delete mode 100644 api/v1beta2/additional_metadata.go create mode 100644 controllers/resources/global.go create mode 100644 controllers/resources/namespaced.go create mode 100644 controllers/resources/processor.go create mode 100644 controllers/resources/processor_finalizer.go create mode 100644 controllers/resources/processor_pruning.go create mode 100644 controllers/resources/processor_section.go diff --git a/api/v1beta2/additional_metadata.go b/api/v1beta2/additional_metadata.go deleted file mode 100644 index 9d708e73..00000000 --- a/api/v1beta2/additional_metadata.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package v1beta2 - -type AdditionalMetadataSpec struct { - Labels map[string]string `json:"labels,omitempty"` - Annotations map[string]string `json:"annotations,omitempty"` -} diff --git a/api/v1beta2/tenantresource_namespaced.go b/api/v1beta2/tenantresource_namespaced.go index d0852613..9a0d18d6 100644 --- a/api/v1beta2/tenantresource_namespaced.go +++ b/api/v1beta2/tenantresource_namespaced.go @@ -6,6 +6,8 @@ package v1beta2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + + "github.com/clastix/capsule/pkg/api" ) // TenantResourceSpec defines the desired state of TenantResource. @@ -32,7 +34,7 @@ type ResourceSpec struct { RawItems []RawExtension `json:"rawItems,omitempty"` // Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be // added to the replicated resources. - AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` + AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` } // +kubebuilder:validation:XEmbeddedResource diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index f0994bb1..b6c0df5d 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -15,35 +15,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadataSpec. -func (in *AdditionalMetadataSpec) DeepCopy() *AdditionalMetadataSpec { - if in == nil { - return nil - } - out := new(AdditionalMetadataSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSpec) { *out = *in @@ -525,7 +496,7 @@ func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) { } if in.AdditionalMetadata != nil { in, out := &in.AdditionalMetadata, &out.AdditionalMetadata - *out = new(AdditionalMetadataSpec) + *out = new(api.AdditionalMetadataSpec) (*in).DeepCopyInto(*out) } } diff --git a/controllers/resources/global.go b/controllers/resources/global.go new file mode 100644 index 00000000..be485319 --- /dev/null +++ b/controllers/resources/global.go @@ -0,0 +1,186 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "context" + + "github.com/hashicorp/go-multierror" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +type Global struct { + client client.Client + processor Processor +} + +func (r *Global) enqueueRequestFromTenant(object client.Object) (reqs []reconcile.Request) { + tnt := object.(*capsulev1beta2.Tenant) //nolint:forcetypeassert + + resList := capsulev1beta2.GlobalTenantResourceList{} + if err := r.client.List(context.Background(), &resList); err != nil { + return nil + } + + set := sets.NewString() + + for _, res := range resList.Items { + selector, err := metav1.LabelSelectorAsSelector(&res.Spec.TenantSelector) + if err != nil { + continue + } + + if selector.Matches(labels.Set(tnt.GetLabels())) { + set.Insert(res.GetName()) + } + } + // No need of ordered value here + for res := range set { + reqs = append(reqs, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: res, + }, + }) + } + + return reqs +} + +func (r *Global) SetupWithManager(mgr ctrl.Manager) error { + unstructuredCachingClient, err := client.NewDelegatingClient( + client.NewDelegatingClientInput{ + Client: mgr.GetClient(), + CacheReader: mgr.GetCache(), + CacheUnstructured: true, + }, + ) + if err != nil { + return err + } + + r.client = mgr.GetClient() + r.processor = Processor{ + client: r.client, + unstructuredClient: unstructuredCachingClient, + } + + return ctrl.NewControllerManagedBy(mgr). + For(&capsulev1beta2.GlobalTenantResource{}). + Watches(&source.Kind{Type: &capsulev1beta2.Tenant{}}, handler.EnqueueRequestsFromMapFunc(r.enqueueRequestFromTenant)). + Complete(r) +} + +func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + log.Info("start processing") + + tntResource := capsulev1beta2.GlobalTenantResource{} + if err := r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { + if errors.IsNotFound(err) { + log.Info("Request object not found, could have been deleted after reconcile request") + + return reconcile.Result{}, nil + } + + return reconcile.Result{}, err + } + // Adding the default value for the status + if tntResource.Status.ProcessedItems == nil { + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, 0) + } + // Handling the finalizer section for the given GlobalTenantResource + enqueueBack, err := r.processor.HandleFinalizer(ctx, &tntResource, *tntResource.Spec.PruningOnDelete, tntResource.Status.ProcessedItems) + if err != nil || enqueueBack { + return reconcile.Result{}, err + } + // Retrieving the list of the Tenants up to the selector provided by the GlobalTenantResource resource. + tntSelector, err := metav1.LabelSelectorAsSelector(&tntResource.Spec.TenantSelector) + if err != nil { + log.Error(err, "cannot create MatchingLabelsSelector for Global filtering") + + return reconcile.Result{}, err + } + + tntList := capsulev1beta2.TenantList{} + if err = r.client.List(ctx, &tntList, &client.MatchingLabelsSelector{Selector: tntSelector}); err != nil { + log.Error(err, "cannot list Tenants matching the provided selector") + + return reconcile.Result{}, err + } + // This is the list of newer Tenants that are matching the provided GlobalTenantResource Selector: + // upon replication and pruning, this will be updated in the status of the resource. + tntSet := sets.NewString() + + err = new(multierror.Error) + // A TenantResource is made of several Resource sections, each one with specific options: + // the Status can be updated only in case of no errors across all of them to guarantee a valid and coherent status. + processedItems := sets.NewString() + + for index, resource := range tntResource.Spec.Resources { + tenantLabel, labelErr := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{}) + if labelErr != nil { + log.Error(labelErr, "expected label for selection") + + return reconcile.Result{}, labelErr + } + + for _, tnt := range tntList.Items { + tntSet.Insert(tnt.GetName()) + + items, sectionErr := r.processor.HandleSection(ctx, tnt, true, tenantLabel, index, resource) + if sectionErr != nil { + // Upon a process error storing the last error occurred and continuing to iterate, + // avoid to block the whole processing. + err = multierror.Append(err, sectionErr) + } else { + processedItems.Insert(items...) + } + } + } + + if err.(*multierror.Error).ErrorOrNil() != nil { //nolint:errorlint,forcetypeassert + log.Error(err, "unable to replicate the requested resources") + + return reconcile.Result{}, err + } + + shouldUpdateStatus := !sets.NewString(tntResource.Status.SelectedTenants...).Equal(tntSet) + + if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems, processedItems) { + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) + + for _, item := range processedItems.List() { + if or := (capsulev1beta2.ObjectReferenceStatus{}); or.ParseFromString(item) == nil { + tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or) + } + } + + shouldUpdateStatus = true + } + + if shouldUpdateStatus { + tntResource.Status.SelectedTenants = tntSet.List() + + if updateErr := r.client.Status().Update(ctx, &tntResource); updateErr != nil { + log.Error(updateErr, "unable to update TenantResource status") + } + } + + log.Info("processing completed") + + return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil +} diff --git a/controllers/resources/namespaced.go b/controllers/resources/namespaced.go new file mode 100644 index 00000000..891d3a3d --- /dev/null +++ b/controllers/resources/namespaced.go @@ -0,0 +1,144 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "context" + + "github.com/hashicorp/go-multierror" + apierr "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/util/retry" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +type Namespaced struct { + client client.Client + finalizer Processor +} + +func (r *Namespaced) SetupWithManager(mgr ctrl.Manager) error { + unstructuredCachingClient, err := client.NewDelegatingClient( + client.NewDelegatingClientInput{ + Client: mgr.GetClient(), + CacheReader: mgr.GetCache(), + CacheUnstructured: true, + }, + ) + if err != nil { + return err + } + + r.client = mgr.GetClient() + r.finalizer = Processor{ + client: r.client, + unstructuredClient: unstructuredCachingClient, + } + + return ctrl.NewControllerManagedBy(mgr). + For(&capsulev1beta2.TenantResource{}). + Complete(r) +} + +func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + log.Info("start processing") + // Retrieving the TenantResource + tntResource := capsulev1beta2.TenantResource{} + if err := r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { + if apierr.IsNotFound(err) { + log.Info("Request object not found, could have been deleted after reconcile request") + + return reconcile.Result{}, nil + } + + log.Error(err, "cannot retrieve capsulev1beta2.TenantResource") + + return reconcile.Result{}, err + } + // Adding the default value for the status + if tntResource.Status.ProcessedItems == nil { + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, 0) + } + // Handling the finalizer section for the given TenantResource + enqueueBack, err := r.finalizer.HandleFinalizer(ctx, &tntResource, *tntResource.Spec.PruningOnDelete, tntResource.Status.ProcessedItems) + if err != nil || enqueueBack { + return reconcile.Result{}, err + } + // Retrieving the parent of the Global Resource: + // can be owned, or being deployed in one of its Namespace. + tl := &capsulev1beta2.TenantList{} + if err = r.client.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", tntResource.GetNamespace())}); err != nil { + log.Error(err, "unable to detect the Global for the given TenantResource") + + return reconcile.Result{}, err + } + + if len(tl.Items) == 0 { + log.Info("skipping sync, the current Namespace is not belonging to any Global") + + return reconcile.Result{}, nil + } + + err = new(multierror.Error) + // A TenantResource is made of several Resource sections, each one with specific options: + // the Status can be updated only in case of no errors across all of them to guarantee a valid and coherent status. + processedItems := sets.NewString() + + tenantLabel, labelErr := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{}) + if labelErr != nil { + log.Error(labelErr, "expected label for selection") + + return reconcile.Result{}, labelErr + } + + for index, resource := range tntResource.Spec.Resources { + items, sectionErr := r.finalizer.HandleSection(ctx, tl.Items[0], false, tenantLabel, index, resource) + if sectionErr != nil { + // Upon a process error storing the last error occurred and continuing to iterate, + // avoid to block the whole processing. + err = multierror.Append(err, sectionErr) + } else { + processedItems.Insert(items...) + } + } + + if err.(*multierror.Error).ErrorOrNil() != nil { //nolint:errorlint,forcetypeassert + log.Error(err, "unable to replicate the requested resources") + + return reconcile.Result{}, err + } + + if r.finalizer.HandlePruning(ctx, tntResource.Status.ProcessedItems, processedItems) { + statusErr := retry.RetryOnConflict(retry.DefaultRetry, func() (err error) { + if err = r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { + return err + } + + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) + + for _, item := range processedItems.List() { + if or := (capsulev1beta2.ObjectReferenceStatus{}); or.ParseFromString(item) == nil { + tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or) + } + } + + return r.client.Status().Update(ctx, &tntResource) + }) + if statusErr != nil { + log.Error(statusErr, "unable to update TenantResource status") + } + } + + log.Info("processing completed") + + return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil +} diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go new file mode 100644 index 00000000..ed71c665 --- /dev/null +++ b/controllers/resources/processor.go @@ -0,0 +1,17 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + finalizer = "capsule.clastix.io/resources" +) + +type Processor struct { + client client.Client + unstructuredClient client.Client +} diff --git a/controllers/resources/processor_finalizer.go b/controllers/resources/processor_finalizer.go new file mode 100644 index 00000000..edc666bb --- /dev/null +++ b/controllers/resources/processor_finalizer.go @@ -0,0 +1,54 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "context" + + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +func (r *Processor) HandleFinalizer(ctx context.Context, obj client.Object, shouldPrune bool, items []capsulev1beta2.ObjectReferenceStatus) (enqueueBack bool, err error) { + log := ctrllog.FromContext(ctx) + // If the object has been marked for deletion, + // we have to clean up the created resources before removing the finalizer. + if obj.GetDeletionTimestamp() != nil { + log.Info("pruning prior finalizer removal") + + if shouldPrune { + _ = r.HandlePruning(ctx, items, nil) + } + + obj.SetFinalizers(nil) + + if err = r.client.Update(ctx, obj); err != nil { + log.Error(err, "cannot remove finalizer") + + return true, err + } + + return true, nil + } + // When the pruning for the given resource is enabled, a finalizer is required when the TenantResource is marked + // for deletion: this allows to perform a clean-up of all the underlying resources. + if shouldPrune && !sets.NewString(obj.GetFinalizers()...).Has(finalizer) { + obj.SetFinalizers(append(obj.GetFinalizers(), finalizer)) + + if err = r.client.Update(ctx, obj); err != nil { + log.Error(err, "cannot add finalizer") + + return true, err + } + + log.Info("added finalizer, enqueuing back for processing") + + return true, nil + } + + return false, nil +} diff --git a/controllers/resources/processor_pruning.go b/controllers/resources/processor_pruning.go new file mode 100644 index 00000000..a6a37c83 --- /dev/null +++ b/controllers/resources/processor_pruning.go @@ -0,0 +1,67 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "context" + + apierr "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +func (r *Processor) HandlePruning(ctx context.Context, current []capsulev1beta2.ObjectReferenceStatus, desired sets.String) (updateStatus bool) { + log := ctrllog.FromContext(ctx) + // The status items are the actual replicated resources, these must be collected in order to perform the resulting + // diff that will be cleaned-up. + status := sets.NewString() + + for _, item := range current { + status.Insert(item.String()) + } + + diff := status.Difference(desired) + // We don't want to trigger a reconciliation of the Status every time, + // rather, only in case of a difference between the processed and the actual status. + // This can happen upon the first reconciliation, or a removal, or a change, of a resource. + updateStatus = diff.Len() > 0 || status.Len() != desired.Len() + + if diff.Len() > 0 { + log.Info("starting processing pruning", "length", diff.Len()) + } + + // The outer resources must be removed, iterating over these to clean-up + for item := range diff { + or := capsulev1beta2.ObjectReferenceStatus{} + if err := or.ParseFromString(item); err != nil { + log.Error(err, "unable to parse resource to prune", "resource", item) + + continue + } + + obj := unstructured.Unstructured{} + obj.SetNamespace(or.Namespace) + obj.SetName(or.Name) + obj.SetGroupVersionKind(schema.FromAPIVersionAndKind(or.APIVersion, or.Kind)) + + if err := r.unstructuredClient.Delete(ctx, &obj); err != nil { + if apierr.IsNotFound(err) { + // Object may have been already deleted, we can ignore this error + continue + } + + log.Error(err, "unable to prune resource", "resource", item) + + continue + } + + log.Info("resource has been pruned", "resource", item) + } + + return updateStatus +} diff --git a/controllers/resources/processor_section.go b/controllers/resources/processor_section.go new file mode 100644 index 00000000..77c1b662 --- /dev/null +++ b/controllers/resources/processor_section.go @@ -0,0 +1,223 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-multierror" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant, allowCrossNamespaceSelection bool, tenantLabel string, resourceIndex int, spec capsulev1beta2.ResourceSpec) ([]string, error) { + log := ctrllog.FromContext(ctx) + + var err error + // Creating Namespace selector + var selector labels.Selector + + if spec.NamespaceSelector != nil { + selector, err = metav1.LabelSelectorAsSelector(spec.NamespaceSelector) + if err != nil { + log.Error(err, "cannot create Namespace selector for Namespace filtering and resource replication", "index", resourceIndex) + + return nil, err + } + } else { + selector = labels.NewSelector() + } + // Resources can be replicated only on Namespaces belonging to the same Global: + // preventing a boundary cross by enforcing the selection. + tntRequirement, err := labels.NewRequirement(tenantLabel, selection.Equals, []string{tnt.GetName()}) + if err != nil { + log.Error(err, "unable to create requirement for Namespace filtering and resource replication", "index", resourceIndex) + + return nil, err + } + + selector = selector.Add(*tntRequirement) + // Selecting the targeted Namespace according to the TenantResource specification. + namespaces := corev1.NamespaceList{} + if err = r.client.List(ctx, &namespaces, client.MatchingLabelsSelector{Selector: selector}); err != nil { + log.Error(err, "cannot retrieve Namespaces for resource", "index", resourceIndex) + + return nil, err + } + // Generating additional metadata + objAnnotations, objLabels := map[string]string{}, map[string]string{} + + if spec.AdditionalMetadata != nil { + objAnnotations = spec.AdditionalMetadata.Annotations + objLabels = spec.AdditionalMetadata.Labels + } + + objAnnotations[tenantLabel] = tnt.GetName() + + objLabels["capsule.clastix.io/resources"] = fmt.Sprintf("%d", resourceIndex) + objLabels[tenantLabel] = tnt.GetName() + // processed will contain the sets of resources replicated, both for the raw and the Namespaced ones: + // these are required to perform a final pruning once the replication has been occurred. + processed := sets.NewString() + + tntNamespaces := sets.NewString(tnt.Status.Namespaces...) + + syncErr := new(multierror.Error) + + for nsIndex, item := range spec.NamespacedItems { + keysAndValues := []interface{}{"index", nsIndex, "namespace", item.Namespace} + // A TenantResource is created by a TenantOwner, and potentially, they could point to a resource in a non-owned + // Namespace: this must be blocked by checking it this is the case. + if !allowCrossNamespaceSelection && !tntNamespaces.Has(item.Namespace) { + log.Info("skipping processing of namespacedItem, referring a Namespace that is not part of the given Global", keysAndValues...) + + continue + } + // Namespaced Items are relying on selecting resources, rather than specifying a specific name: + // creating it to get used by the client List action. + itemSelector, selectorErr := metav1.LabelSelectorAsSelector(&item.Selector) + if err != nil { + log.Error(selectorErr, "cannot create Selector for namespacedItem", keysAndValues...) + + continue + } + + objs := unstructured.UnstructuredList{} + objs.SetGroupVersionKind(schema.FromAPIVersionAndKind(item.APIVersion, fmt.Sprintf("%sList", item.Kind))) + + if clientErr := r.unstructuredClient.List(ctx, &objs, client.InNamespace(item.Namespace), client.MatchingLabelsSelector{Selector: itemSelector}); clientErr != nil { + log.Error(clientErr, "cannot retrieve object for namespacedItem", keysAndValues...) + + syncErr = multierror.Append(syncErr, clientErr) + + continue + } + + multiErr := new(multierror.Group) + // Iterating over all the retrieved objects from the resource spec to get replicated in all the selected Namespaces: + // in case of error during the create or update function, this will be appended to the list of errors. + for _, o := range objs.Items { + obj := o + + multiErr.Go(func() error { + nsItems, nsErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) + if nsErr != nil { + log.Error(err, "unable to sync namespacedItems", keysAndValues...) + + return nsErr + } + + processed.Insert(nsItems...) + + return nil + }) + } + + if objsErr := multiErr.Wait(); objsErr != nil { + syncErr = multierror.Append(syncErr, objsErr) + } + } + + codecFactory := serializer.NewCodecFactory(r.client.Scheme()) + + for rawIndex, item := range spec.RawItems { + obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} + + if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(item.Raw, nil, &obj); decodeErr != nil { + log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) + + syncErr = multierror.Append(syncErr, decodeErr) + + continue + } + + syncedRaw, rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) + if rawErr != nil { + log.Info("unable to sync rawItem", keysAndValues...) + // In case of error processing an item in one of any selected Namespaces, storing it to report it lately + // to the upper call to ensure a partial sync that will be fixed by a subsequent reconciliation. + syncErr = multierror.Append(syncErr, rawErr) + } else { + processed.Insert(syncedRaw...) + } + } + + return processed.List(), syncErr.ErrorOrNil() +} + +// createOrUpdate replicates the provided unstructured object to all the provided Namespaces: +// this function mimics the CreateOrUpdate, by retrieving the object to understand if it must be created or updated, +// along adding the additional metadata, if required. +func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string, annotations map[string]string, namespaces corev1.NamespaceList) ([]string, error) { + log := ctrllog.FromContext(ctx) + + errGroup := new(multierror.Group) + + var items []string + + for _, item := range namespaces.Items { + ns := item.GetName() + + errGroup.Go(func() (err error) { + actual, desired := obj.DeepCopy(), obj.DeepCopy() + // Using a deferred function to properly log the results, and adding the item to the processed set. + defer func() { + keysAndValues := []interface{}{"resource", fmt.Sprintf("%s/%s", ns, desired.GetName())} + + if err != nil { + log.Error(err, "unable to replicate resource", keysAndValues...) + + return + } + + log.Info("resource has been replicated", keysAndValues...) + + replicatedItem := &capsulev1beta2.ObjectReferenceStatus{ + Name: obj.GetName(), + } + replicatedItem.Kind = obj.GetKind() + replicatedItem.Namespace = ns + replicatedItem.APIVersion = obj.GetAPIVersion() + + items = append(items, replicatedItem.String()) + }() + + actual.SetNamespace(ns) + + _, err = controllerutil.CreateOrUpdate(ctx, r.unstructuredClient, actual, func() error { + UID := actual.GetUID() + + actual.SetUnstructuredContent(desired.Object) + actual.SetNamespace(ns) + actual.SetLabels(labels) + actual.SetAnnotations(annotations) + actual.SetResourceVersion("") + actual.SetUID(UID) + + return nil + }) + + return + }) + } + // Wait returns *multierror.Error that implements stdlib error: + // the nil check must be performed down here rather than at the caller level to avoid wrong casting. + if err := errGroup.Wait(); err != nil { + return items, err + } + + return items, nil +} diff --git a/go.sum b/go.sum index 06ade127..1972247b 100644 --- a/go.sum +++ b/go.sum @@ -694,7 +694,6 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index 7cb64a83..ccf7029a 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ import ( capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" configcontroller "github.com/clastix/capsule/controllers/config" rbaccontroller "github.com/clastix/capsule/controllers/rbac" + "github.com/clastix/capsule/controllers/resources" servicelabelscontroller "github.com/clastix/capsule/controllers/servicelabels" tenantcontroller "github.com/clastix/capsule/controllers/tenant" tlscontroller "github.com/clastix/capsule/controllers/tls" @@ -266,6 +267,16 @@ func main() { os.Exit(1) } + if err = (&resources.Global{}).SetupWithManager(manager); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "resources.Global") + os.Exit(1) + } + + if err = (&resources.Namespaced{}).SetupWithManager(manager); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "resources.Namespaced") + os.Exit(1) + } + setupLog.Info("starting manager") if err = manager.Start(ctx); err != nil { diff --git a/pkg/indexer/indexer.go b/pkg/indexer/indexer.go index 2e3e88f2..a80c7497 100644 --- a/pkg/indexer/indexer.go +++ b/pkg/indexer/indexer.go @@ -16,6 +16,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/indexer/ingress" "github.com/clastix/capsule/pkg/indexer/namespace" "github.com/clastix/capsule/pkg/indexer/tenant" @@ -29,7 +31,8 @@ type CustomIndexer interface { func AddToManager(ctx context.Context, log logr.Logger, mgr manager.Manager) error { indexers := []CustomIndexer{ - tenant.NamespacesReference{}, + tenant.NamespacesReference{Obj: &capsulev1beta1.Tenant{}}, + tenant.NamespacesReference{Obj: &capsulev1beta2.Tenant{}}, tenant.OwnerReference{}, namespace.OwnerReference{}, ingress.HostnamePath{Obj: &extensionsv1beta1.Ingress{}}, diff --git a/pkg/indexer/tenant/namespaces.go b/pkg/indexer/tenant/namespaces.go index f292d07b..d4388f92 100644 --- a/pkg/indexer/tenant/namespaces.go +++ b/pkg/indexer/tenant/namespaces.go @@ -6,13 +6,15 @@ package tenant import ( "sigs.k8s.io/controller-runtime/pkg/client" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) -type NamespacesReference struct{} +type NamespacesReference struct { + Obj client.Object +} func (o NamespacesReference) Object() client.Object { - return &capsulev1beta1.Tenant{} + return o.Obj } func (o NamespacesReference) Field() string { @@ -22,12 +24,6 @@ func (o NamespacesReference) Field() string { // nolint:forcetypeassert func (o NamespacesReference) Func() client.IndexerFunc { return func(object client.Object) []string { - namespaces := object.(*capsulev1beta1.Tenant).DeepCopy().Status.Namespaces - - if namespaces == nil { - return []string{} - } - - return namespaces + return object.(api.Tenant).GetNamespaces() } } From 9f1165ea40bc5c7a219cd8b8db205c6b295a591c Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:55:54 +0200 Subject: [PATCH 025/153] test: globaltenantresource and tenantresource support --- e2e/globaltenantresource_test.go | 300 ++++++++++++++++++++++++++++ e2e/suite_test.go | 9 +- e2e/tenantresource_test.go | 326 +++++++++++++++++++++++++++++++ 3 files changed, 630 insertions(+), 5 deletions(-) create mode 100644 e2e/globaltenantresource_test.go create mode 100644 e2e/tenantresource_test.go diff --git a/e2e/globaltenantresource_test.go b/e2e/globaltenantresource_test.go new file mode 100644 index 00000000..0f2c9144 --- /dev/null +++ b/e2e/globaltenantresource_test.go @@ -0,0 +1,300 @@ +//go:build e2e + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + "fmt" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/api" +) + +var _ = Describe("Creating a GlobalTenantResource object", func() { + solar := &capsulev1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "energy-solar", + Labels: map[string]string{ + "replicate": "true", + }, + }, + Spec: capsulev1beta1.TenantSpec{ + Owners: capsulev1beta1.OwnerListSpec{ + { + Name: "solar-user", + Kind: "User", + }, + }, + }, + } + + wind := &capsulev1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "energy-wind", + Labels: map[string]string{ + "replicate": "true", + }, + }, + Spec: capsulev1beta1.TenantSpec{ + Owners: capsulev1beta1.OwnerListSpec{ + { + Name: "wind-user", + Kind: "User", + }, + }, + }, + } + + namespacedItem := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummy-secret", + Namespace: "default", + Labels: map[string]string{ + "replicate": "true", + }, + }, + Type: corev1.SecretTypeOpaque, + } + + gtr := &capsulev1beta2.GlobalTenantResource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "replicate-energies", + }, + Spec: capsulev1beta2.GlobalTenantResourceSpec{ + TenantSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "replicate": "true", + }, + }, + TenantResourceSpec: capsulev1beta2.TenantResourceSpec{ + ResyncPeriod: metav1.Duration{Duration: time.Minute}, + PruningOnDelete: pointer.Bool(true), + Resources: []capsulev1beta2.ResourceSpec{ + { + NamespacedItems: []capsulev1beta2.ObjectReference{ + { + ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{ + Kind: "Secret", + Namespace: "default", + APIVersion: "v1", + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "replicate": "true", + }, + }, + }, + }, + RawItems: []capsulev1beta2.RawExtension{ + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-1", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-2", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-3", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + }, + AdditionalMetadata: &api.AdditionalMetadataSpec{ + Labels: map[string]string{ + "labels.energy.io": "replicate", + }, + Annotations: map[string]string{ + "annotations.energy.io": "replicate", + }, + }, + }, + }, + }, + }, + } + + JustBeforeEach(func() { + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), solar) + }).Should(Succeed()) + + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), wind) + }).Should(Succeed()) + + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), gtr) + }).Should(Succeed()) + + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), namespacedItem) + }).Should(Succeed()) + }) + + JustAfterEach(func() { + Expect(k8sClient.Delete(context.TODO(), solar)).Should(Succeed()) + Expect(k8sClient.Delete(context.TODO(), wind)).Should(Succeed()) + Expect(k8sClient.Delete(context.TODO(), gtr)).Should(Succeed()) + Expect(k8sClient.Delete(context.TODO(), namespacedItem)).Should(Succeed()) + }) + + It("should replicate resources to all Tenants", func() { + solarNs, windNs := []string{"solar-one", "solar-two", "solar-three"}, []string{"wind-one", "wind-two", "wind-three"} + + By("creating solar Namespaces", func() { + for _, ns := range solarNs { + NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, solar.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + } + }) + + By("creating wind Namespaces", func() { + for _, ns := range windNs { + NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, wind.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + } + }) + + for _, ns := range append(solarNs, windNs...) { + By(fmt.Sprintf("waiting for replicated resources in %s Namespace", ns), func() { + Eventually(func() []corev1.Secret { + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + if err != nil { + return nil + } + + secrets := corev1.SecretList{} + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns}) + if err != nil { + return nil + } + + return secrets.Items + }, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4)) + }) + } + + By("removing a Namespace from labels", func() { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: wind.GetName()}, wind)).ToNot(HaveOccurred()) + + wind.SetLabels(nil) + Expect(k8sClient.Update(context.TODO(), wind)).ToNot(HaveOccurred()) + + By("expecting no more items in the wind Tenant namespaces due to label update", func() { + for _, ns := range windNs { + Eventually(func() []corev1.Secret { + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + if err != nil { + return nil + } + + secrets := corev1.SecretList{} + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns}) + if err != nil { + return nil + } + + return secrets.Items + }, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(0)) + } + }) + }) + + By("using a Namespace selector", func() { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: gtr.GetName()}, gtr)).ToNot(HaveOccurred()) + + gtr.Spec.Resources[0].NamespaceSelector = &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": "solar-three", + }, + } + + Expect(k8sClient.Update(context.TODO(), gtr)).ToNot(HaveOccurred()) + + checkFn := func(ns string) func() []corev1.Secret { + return func() []corev1.Secret { + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + if err != nil { + return nil + } + + secrets := corev1.SecretList{} + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns}) + if err != nil { + return nil + } + + return secrets.Items + } + } + + for _, ns := range []string{"solar-one", "solar-two"} { + Eventually(checkFn(ns), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(0)) + } + + Eventually(checkFn("solar-three"), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4)) + }) + + By("checking if replicated object have annotations and labels", func() { + for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} { + secret := corev1.Secret{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: "solar-three"}, &secret)).ToNot(HaveOccurred()) + + for k, v := range gtr.Spec.Resources[0].AdditionalMetadata.Labels { + _, err := HaveKeyWithValue(k, v).Match(secret.GetLabels()) + Expect(err).ToNot(HaveOccurred()) + } + + for k, v := range gtr.Spec.Resources[0].AdditionalMetadata.Annotations { + _, err := HaveKeyWithValue(k, v).Match(secret.GetAnnotations()) + Expect(err).ToNot(HaveOccurred()) + } + } + }) + }) +}) diff --git a/e2e/suite_test.go b/e2e/suite_test.go index f3af00af..4748f5b3 100644 --- a/e2e/suite_test.go +++ b/e2e/suite_test.go @@ -22,6 +22,7 @@ import ( capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -57,11 +58,9 @@ var _ = BeforeSuite(func(done Done) { Expect(err).ToNot(HaveOccurred()) Expect(cfg).ToNot(BeNil()) - err = capsulev1beta1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - err = capsulev1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) + Expect(capsulev1alpha1.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) + Expect(capsulev1beta1.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) + Expect(capsulev1beta2.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).ToNot(HaveOccurred()) diff --git a/e2e/tenantresource_test.go b/e2e/tenantresource_test.go new file mode 100644 index 00000000..e1cb8c1a --- /dev/null +++ b/e2e/tenantresource_test.go @@ -0,0 +1,326 @@ +//go:build e2e + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + "fmt" + "math/rand" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/api" +) + +var _ = Describe("Creating a TenantResource object", func() { + solar := &capsulev1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "energy-solar", + }, + Spec: capsulev1beta1.TenantSpec{ + Owners: capsulev1beta1.OwnerListSpec{ + { + Name: "solar-user", + Kind: "User", + }, + }, + }, + } + + tntItem := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummy-secret", + Namespace: "solar-system", + Labels: map[string]string{ + "replicate": "true", + }, + }, + Type: corev1.SecretTypeOpaque, + } + + crossNamespaceItem := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cross-reference-secret", + Namespace: "default", + Labels: map[string]string{ + "replicate": "true", + }, + }, + Type: corev1.SecretTypeOpaque, + } + + tr := &capsulev1beta2.TenantResource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "replicate-energies", + Namespace: "solar-system", + }, + Spec: capsulev1beta2.TenantResourceSpec{ + ResyncPeriod: metav1.Duration{Duration: time.Minute}, + PruningOnDelete: pointer.Bool(true), + Resources: []capsulev1beta2.ResourceSpec{ + { + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "replicate": "true", + }, + }, + NamespacedItems: []capsulev1beta2.ObjectReference{ + { + ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{ + Kind: "Secret", + Namespace: "solar-system", + APIVersion: "v1", + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "replicate": "true", + }, + }, + }, + }, + RawItems: []capsulev1beta2.RawExtension{ + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-1", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-2", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + { + RawExtension: runtime.RawExtension{ + Object: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "raw-secret-3", + }, + Type: corev1.SecretTypeOpaque, + }, + }, + }, + }, + AdditionalMetadata: &api.AdditionalMetadataSpec{ + Labels: map[string]string{ + "labels.energy.io": "replicate", + }, + Annotations: map[string]string{ + "annotations.energy.io": "replicate", + }, + }, + }, + }, + }, + } + + JustBeforeEach(func() { + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), solar) + }).Should(Succeed()) + + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), crossNamespaceItem) + }).Should(Succeed()) + }) + + JustAfterEach(func() { + Expect(k8sClient.Delete(context.TODO(), crossNamespaceItem)).Should(Succeed()) + _ = k8sClient.Delete(context.TODO(), solar) + }) + + It("should replicate resources to all Tenant Namespaces", func() { + solarNs := []string{"solar-one", "solar-two", "solar-three"} + + By("creating solar Namespaces", func() { + for _, ns := range append(solarNs, "solar-system") { + NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, solar.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + } + }) + + By("labelling Namespaces", func() { + for _, name := range []string{"solar-one", "solar-two", "solar-three"} { + EventuallyWithOffset(1, func() error { + ns := corev1.Namespace{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name}, &ns)).Should(Succeed()) + + labels := ns.GetLabels() + if labels == nil { + return fmt.Errorf("missing labels") + } + labels["replicate"] = "true" + ns.SetLabels(labels) + + return k8sClient.Update(context.TODO(), &ns) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + } + }) + + By("creating the namespaced item", func() { + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), tntItem) + }).Should(Succeed()) + }) + + By("creating the TenantResource", func() { + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), tr) + }).Should(Succeed()) + }) + + for _, ns := range solarNs { + By(fmt.Sprintf("waiting for replicated resources in %s Namespace", ns), func() { + Eventually(func() []corev1.Secret { + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + if err != nil { + return nil + } + + secrets := corev1.SecretList{} + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns}) + if err != nil { + return nil + } + + return secrets.Items + }, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4)) + }) + } + + By("using a Namespace selector", func() { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tr.GetName(), Namespace: "solar-system"}, tr)).ToNot(HaveOccurred()) + + tr.Spec.Resources[0].NamespaceSelector = &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": "solar-three", + }, + } + + Expect(k8sClient.Update(context.TODO(), tr)).ToNot(HaveOccurred()) + + checkFn := func(ns string) func() []corev1.Secret { + return func() []corev1.Secret { + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + if err != nil { + return nil + } + + secrets := corev1.SecretList{} + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: ns}) + if err != nil { + return nil + } + + return secrets.Items + } + } + + for _, ns := range []string{"solar-one", "solar-two"} { + Eventually(checkFn(ns), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(0)) + } + + Eventually(checkFn("solar-three"), defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4)) + }) + + By("checking if replicated object have annotations and labels", func() { + for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} { + secret := corev1.Secret{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: "solar-three"}, &secret)).ToNot(HaveOccurred()) + + for k, v := range tr.Spec.Resources[0].AdditionalMetadata.Labels { + _, err := HaveKeyWithValue(k, v).Match(secret.GetLabels()) + Expect(err).ToNot(HaveOccurred()) + } + + for k, v := range tr.Spec.Resources[0].AdditionalMetadata.Annotations { + _, err := HaveKeyWithValue(k, v).Match(secret.GetAnnotations()) + Expect(err).ToNot(HaveOccurred()) + } + } + }) + + By("checking that cross-namespace objects are not replicated", func() { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tr.GetName(), Namespace: "solar-system"}, tr)).ToNot(HaveOccurred()) + tr.Spec.Resources[0].NamespacedItems = append(tr.Spec.Resources[0].NamespacedItems, capsulev1beta2.ObjectReference{ + ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{ + Kind: crossNamespaceItem.Kind, + Namespace: crossNamespaceItem.GetName(), + APIVersion: crossNamespaceItem.APIVersion, + }, + Selector: metav1.LabelSelector{ + MatchLabels: crossNamespaceItem.GetLabels(), + }, + }) + + Expect(k8sClient.Update(context.TODO(), tr)).ToNot(HaveOccurred()) + // Ensuring that although the deletion of TenantResource object, + // the replicated objects are not deleted. + Consistently(func() error { + return k8sClient.Get(context.TODO(), types.NamespacedName{Namespace: solarNs[rand.Intn(len(solarNs))], Name: crossNamespaceItem.GetName()}, &corev1.Secret{}) + }, 10*time.Second, time.Second).Should(HaveOccurred()) + }) + + By("checking pruning is deleted", func() { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tr.GetName(), Namespace: "solar-system"}, tr)).ToNot(HaveOccurred()) + Expect(*tr.Spec.PruningOnDelete).Should(BeTrue()) + + tr.Spec.PruningOnDelete = pointer.Bool(false) + + Expect(k8sClient.Update(context.TODO(), tr)).ToNot(HaveOccurred()) + + By("deleting the TenantResource", func() { + // Ensuring that although the deletion of TenantResource object, + // the replicated objects are not deleted. + Expect(k8sClient.Delete(context.TODO(), tr)).Should(Succeed()) + + r, err := labels.NewRequirement("labels.energy.io", selection.DoubleEquals, []string{"replicate"}) + Expect(err).ToNot(HaveOccurred()) + + Consistently(func() []corev1.Secret { + secrets := corev1.SecretList{} + + err = k8sClient.List(context.TODO(), &secrets, &client.ListOptions{LabelSelector: labels.NewSelector().Add(*r), Namespace: "solar-three"}) + Expect(err).ToNot(HaveOccurred()) + + return secrets.Items + }, 10*time.Second, time.Second).Should(HaveLen(4)) + }) + }) + }) +}) From 369feb23c6bead108ad161ad906ba86a72db2c0b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:56:14 +0200 Subject: [PATCH 026/153] docs: globaltenantresource and tenantresource support --- Makefile | 2 +- .../general/{tenant-crd.md => crds-apis.md} | 1121 +++++++++++++++-- docs/content/general/references.md | 2 +- docs/content/general/tutorial.md | 126 ++ docs/gridsome.server.js | 2 +- 5 files changed, 1178 insertions(+), 75 deletions(-) rename docs/content/general/{tenant-crd.md => crds-apis.md} (86%) diff --git a/Makefile b/Makefile index 451cfb3a..c496a090 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ generate: controller-gen $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." apidoc: apidocs-gen - $(APIDOCS_GEN) crdoc --resources config/crd/bases --output docs/content/general/tenant-crd.md --template docs/template/reference-cr.tmpl + $(APIDOCS_GEN) crdoc --resources config/crd/bases --output docs/content/general/crds-apis.md --template docs/template/reference-cr.tmpl # Helm SRC_ROOT = $(shell git rev-parse --show-toplevel) diff --git a/docs/content/general/tenant-crd.md b/docs/content/general/crds-apis.md similarity index 86% rename from docs/content/general/tenant-crd.md rename to docs/content/general/crds-apis.md index 4a34dc2e..b6b091dd 100644 --- a/docs/content/general/tenant-crd.md +++ b/docs/content/general/crds-apis.md @@ -639,14 +639,14 @@ LimitRangeItem defines a min/max usage limit for any resource that matches on ki - annotations + additionalAnnotations map[string]string
false - labels + additionalLabels map[string]string
@@ -1498,14 +1498,14 @@ A scoped-resource selector requirement is a selector that contains values, a sco - annotations + additionalAnnotations map[string]string
false - labels + additionalLabels map[string]string
@@ -1586,6 +1586,10 @@ Resource Types: - [CapsuleConfiguration](#capsuleconfiguration) +- [GlobalTenantResource](#globaltenantresource) + +- [TenantResource](#tenantresource) + - [Tenant](#tenant) @@ -1848,14 +1852,14 @@ Allows to set different name rather than the canonical one for the Capsule confi -## Tenant +## GlobalTenantResource -Tenant is the Schema for the tenants API. +GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. @@ -1875,7 +1879,7 @@ Tenant is the Schema for the tenants API. - + @@ -1884,28 +1888,28 @@ Tenant is the Schema for the tenants API. - + - +
kind stringTenantGlobalTenantResource true
Refer to the Kubernetes API documentation for the fields of the `metadata` field. true
specspec object - TenantSpec defines the desired state of Tenant.
+ GlobalTenantResourceSpec defines the desired state of GlobalTenantResource.
false
statusstatus object - Returns the observed state of the Tenant.
+ GlobalTenantResourceStatus defines the observed state of GlobalTenantResource.
false
-### Tenant.spec +### GlobalTenantResource.spec -TenantSpec defines the desired state of Tenant. +GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. @@ -1917,119 +1921,246 @@ TenantSpec defines the desired state of Tenant. - + - - + + - + - - + + - - + + - - - + +
ownersresources []object - Specifies the owners of the Tenant. Mandatory.
+ Defines the rules to select targeting Namespace, along with the objects that must be replicated.
true
additionalRoleBindings[]objectresyncPeriodstring - Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
+ Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation.
+
+ Default: 60s
falsetrue
containerRegistriesobjectpruningOnDeleteboolean - Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional.
+ When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest.
+
+ Default: true
false
cordonedbooleantenantSelectorobject - Toggling the Tenant resources cordoning, when enable resources cannot be deleted.
+ Defines the Tenant selector used target the tenants on which resources must be propagated.
false
imagePullPolicies[]enum
+ + +### GlobalTenantResource.spec.resources[index] + + + + + + + + + + + + + + + + + - + - - + + - - + + - - - + +
NameTypeDescriptionRequired
additionalMetadataobject - Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
+ Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources.
false
ingressOptionsnamespaceSelector object - Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+ Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted.
false
limitRangesobjectnamespacedItems[]object - Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
+ List of the resources already existing in other Namespaces that must be replicated.
false
namespaceOptionsobjectrawItems[]RawExtension - Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
+ List of raw resources that must be replicated.
false
networkPoliciesobject
+ + +### GlobalTenantResource.spec.resources[index].additionalMetadata + + + +Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + + + + + + + + + + + + + - + - - - + +
NameTypeDescriptionRequired
annotationsmap[string]string - Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
+
false
nodeSelectorlabels map[string]string - Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
+
false
preventDeletionboolean
+ + +### GlobalTenantResource.spec.resources[index].namespaceSelector + + + +Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + + + + + + + + + + + + + - - + + + +
NameTypeDescriptionRequired
matchExpressions[]object - Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
false
priorityClassesobjectmatchLabelsmap[string]string - Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional.
+ matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
false
+ + +### GlobalTenantResource.spec.resources[index].namespaceSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + - - + + - + - - + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
resourceQuotasobjectoperatorstring - Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
falsetrue
serviceOptionsobjectvalues[]string - Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+ values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
false
+ + +### GlobalTenantResource.spec.resources[index].namespacedItems[index] + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + +
NameTypeDescriptionRequired
kindstring + Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+
true
storageClassesnamespacestring + Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+
true
selector object - Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
+ Label selector used to select the given resources in the given Namespace.
+
true
apiVersionstring + API version of the referent.
false
-### Tenant.spec.owners[index] - +### GlobalTenantResource.spec.resources[index].namespacedItems[index].selector +Label selector used to select the given resources in the given Namespace. @@ -2041,28 +2172,874 @@ TenantSpec defines the desired state of Tenant. - - + + - + - - + + + + +
clusterRoles[]stringmatchExpressions[]object - Defines additional cluster-roles for the specific Owner.
+ matchExpressions is a list of label selector requirements. The requirements are ANDed.
truefalse
kindenummatchLabelsmap[string]string - Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
-
- Enum: User, Group, ServiceAccount
+ matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### GlobalTenantResource.spec.resources[index].namespacedItems[index].selector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + - + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
true
nameoperator string - Name of tenant owner.
+ operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### GlobalTenantResource.spec.tenantSelector + + + +Defines the Tenant selector used target the tenants on which resources must be propagated. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### GlobalTenantResource.spec.tenantSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### GlobalTenantResource.status + + + +GlobalTenantResourceStatus defines the observed state of GlobalTenantResource. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
processedItems[]object + List of the replicated resources for the given TenantResource.
+
true
selectedTenants[]string + List of Tenants addressed by the GlobalTenantResource.
+
true
+ + +### GlobalTenantResource.status.processedItems[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindstring + Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+
true
namestring + Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+
true
namespacestring + Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+
true
apiVersionstring + API version of the referent.
+
false
+ +## TenantResource + + + + + + +TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. For such cases, the GlobalTenantResource must be used. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringcapsule.clastix.io/v1beta2true
kindstringTenantResourcetrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + TenantResourceSpec defines the desired state of TenantResource.
+
false
statusobject + TenantResourceStatus defines the observed state of TenantResource.
+
false
+ + +### TenantResource.spec + + + +TenantResourceSpec defines the desired state of TenantResource. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
resources[]object + Defines the rules to select targeting Namespace, along with the objects that must be replicated.
+
true
resyncPeriodstring + Define the period of time upon a second reconciliation must be invoked. Keep in mind that any change to the manifests will trigger a new reconciliation.
+
+ Default: 60s
+
true
pruningOnDeleteboolean + When the replicated resource manifest is deleted, all the objects replicated so far will be automatically deleted. Disable this to keep replicated resources although the deletion of the replication manifest.
+
+ Default: true
+
false
+ + +### TenantResource.spec.resources[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
additionalMetadataobject + Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources.
+
false
namespaceSelectorobject + Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted.
+
false
namespacedItems[]object + List of the resources already existing in other Namespaces that must be replicated.
+
false
rawItems[]RawExtension + List of raw resources that must be replicated.
+
false
+ + +### TenantResource.spec.resources[index].additionalMetadata + + + +Besides the Capsule metadata required by TenantResource controller, defines additional metadata that must be added to the replicated resources. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
annotationsmap[string]string +
+
false
labelsmap[string]string +
+
false
+ + +### TenantResource.spec.resources[index].namespaceSelector + + + +Defines the Namespace selector to select the Tenant Namespaces on which the resources must be propagated. In case of nil value, all the Tenant Namespaces are targeted. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### TenantResource.spec.resources[index].namespaceSelector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### TenantResource.spec.resources[index].namespacedItems[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindstring + Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+
true
namespacestring + Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+
true
selectorobject + Label selector used to select the given resources in the given Namespace.
+
true
apiVersionstring + API version of the referent.
+
false
+ + +### TenantResource.spec.resources[index].namespacedItems[index].selector + + + +Label selector used to select the given resources in the given Namespace. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### TenantResource.spec.resources[index].namespacedItems[index].selector.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + +### TenantResource.status + + + +TenantResourceStatus defines the observed state of TenantResource. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
processedItems[]object + List of the replicated resources for the given TenantResource.
+
true
+ + +### TenantResource.status.processedItems[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindstring + Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+
true
namestring + Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+
true
namespacestring + Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+
true
apiVersionstring + API version of the referent.
+
false
+ +## Tenant + + + + + + +Tenant is the Schema for the tenants API. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringcapsule.clastix.io/v1beta2true
kindstringTenanttrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + TenantSpec defines the desired state of Tenant.
+
false
statusobject + Returns the observed state of the Tenant.
+
false
+ + +### Tenant.spec + + + +TenantSpec defines the desired state of Tenant. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
owners[]object + Specifies the owners of the Tenant. Mandatory.
+
true
additionalRoleBindings[]object + Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
+
false
containerRegistriesobject + Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional.
+
false
cordonedboolean + Toggling the Tenant resources cordoning, when enable resources cannot be deleted.
+
false
imagePullPolicies[]enum + Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
+
false
ingressOptionsobject + Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
+
false
limitRangesobject + Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
+
false
namespaceOptionsobject + Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
+
false
networkPoliciesobject + Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
+
false
nodeSelectormap[string]string + Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
+
false
preventDeletionboolean + Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
+
false
priorityClassesobject + Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional.
+
false
resourceQuotasobject + Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
+
false
serviceOptionsobject + Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
+
false
storageClassesobject + Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
+
false
+ + +### Tenant.spec.owners[index] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/content/general/references.md b/docs/content/general/references.md index bc91c311..8b959c7c 100644 --- a/docs/content/general/references.md +++ b/docs/content/general/references.md @@ -6,7 +6,7 @@ Reference document for Capsule Operator configuration Capsule operator uses a Custom Resources Definition (CRD) for _Tenants_. Tenants are cluster wide resources, so you need cluster level permissions to work with tenants. -You can learn about tenant CRDs in the following [section](./tenant-crd) +You can learn about tenant CRDs in the following [section](./crds-apis) ## Capsule Configuration diff --git a/docs/content/general/tutorial.md b/docs/content/general/tutorial.md index 4392cae7..7880afea 100644 --- a/docs/content/general/tutorial.md +++ b/docs/content/general/tutorial.md @@ -1724,6 +1724,132 @@ spec: EOF ``` +## Replicating resources across a set of Tenants' Namespaces + +When developing an Internal Developer Platform the Platform Administrator could want to propagate a set of resources. +These could be Secret, ConfigMap, or other kinds of resources that the tenants would require to use the platform. + +> A generic example could be the container registry secrets, especially in the context where the Tenants can just use a specific registry. + +Starting from Capsule v0.2.0, a new set of Custom Resource Definitions have been introduced, such as the `GlobalTenantResource`, let's start with a potential use-case using the personas described at the beginning of this document. + +**Bill** created the Tenants for **Alice** using the `Tenant` CRD, and labels these resources using the following command: + +``` +$: kubectl label tnt/oil energy=fossil +tenant oil labeled + +$: kubectl label tnt/gas energy=fossil +tenant oil labeled +``` + +In the said scenario, these Tenants must use container images from a trusted registry, and that would require the usage of specific credentials for the image pull. + +The said container registry is deployed in the cluster in the namespace `harbor-system`, and this Namespace contains all image pull secret for each Tenant, e.g.: a secret named `harbor-system/fossil-pull-secret` as follows. + +``` +$: kubectl -n harbor-system get secret --show-labels +NAME TYPE DATA AGE LABELS +fossil-pull-secret Opaque 1 28s tenant=fossil +``` + +These credentials would be distributed to the Tenant owners manually, or vice-versa, the owners would require those. +Such a scenario would be against the concept of the self-service solution offered by Capsule, and **Bill** can solve this by creating the `GlobalTenantResource` as follows. + +```yaml +apiVersion: capsule.clastix.io/v1beta2 +kind: GlobalTenantResource +metadata: + name: fossil-pull-secrets +spec: + tenantSelector: + matchLabels: + energy: fossil + resyncPeriod: 60s + resources: + - namespacedItems: + - apiVersion: v1 + kind: Secret + namespace: harbor-system + selector: + matchLabels: + tenant: fossil +``` + +A full reference of the API is available in the [CRDs API section](/docs/general/crds-apis), just explaining the expected behaviour and the resulting outcome: + +> Capsule will select all the Tenant resources according to the key `tenantSelector`. +> Each object defined in the `namespacedItems` and matching the provided `selector` will be replicated into each Namespace bounded to the selected Tenants. +> Capsule will check every 60 seconds if the resources are replicated and in sync, as defined in the key `resyncPeriod`. + +The `GlobalTenantResource` is a cluster-scoped resource, thus it has been designed for cluster administrators and cannot be used by Tenant owners: for that purpose, the `TenantResource` one can help. + +## Replicating resources across Namespaces of a Tenant + +Although Capsule is supporting a few amounts of personas, it can be used to allow building an Internal Developer Platform used barely by Tenant owners, or users created by these thanks to Service Account. + +In a such scenario, a Tenant Owner would like to distribute resources across all the Namespace of their Tenant, without the need to establish a manual procedure, or the need for writing a custom automation. + +The Namespaced-scope API `TenantResource` allows to replicate resources across the Tenant's Namespace. + +> The Tenant owners must have proper RBAC configured in order to create, get, update, and delete their `TenantResource` CRD instances. +> This can be achieved using the Tenant key `additionalRoleBindings` or a custom Tenant owner role, compared to the default one (`admin`). + +For our example, **Alice**, the project lead for the `solar` tenant, wants to provision automatically a **DataBase** resource for each Namespace of their Tenant: these are the Namespace list. + +``` +$: kubectl get namespaces -l capsule.clastix.io/tenant=solar --show-labels +NAME STATUS AGE LABELS +solar-1 Active 59s capsule.clastix.io/tenant=solar,environment=production,kubernetes.io/metadata.name=solar-1,name=solar-1 +solar-2 Active 58s capsule.clastix.io/tenant=solar,environment=production,kubernetes.io/metadata.name=solar-2,name=solar-2 +solar-system Active 62s capsule.clastix.io/tenant=solar,kubernetes.io/metadata.name=solar-system,name=solar-system +``` + +**Alice** creates a `TenantResource` in the Tenant namespace `solar-system` as follows. + +```yaml +apiVersion: capsule.clastix.io/v1beta2 +kind: TenantResource +metadata: + name: solar-db + namespace: solar-system +spec: + resyncPeriod: 60s + resources: + - namespaceSelector: + matchLabels: + environment: production + rawItems: + - apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + metadata: + name: postgresql + spec: + description: PostgreSQL cluster for the Solar project + instances: 3 + postgresql: + pg_hba: + - hostssl app all all cert + primaryUpdateStrategy: unsupervised + storage: + size: 1Gi +``` + +The expected result will be the object `Cluster` for the API version `postgresql.cnpg.io/v1` to get created in all the Solar tenant namespaces matching the label selector declared by the key `namespaceSelector`. + +``` +$: kubectl get clusters.postgresql.cnpg.io -A +NAMESPACE NAME AGE INSTANCES READY STATUS PRIMARY +solar-1 postgresql 80s 3 3 Cluster in healthy state postgresql-1 +solar-2 postgresql 80s 3 3 Cluster in healthy state postgresql-1 +``` + +The `TenantResource` object has been created in the namespace `solar-system` that doesn't satisfy the Namespace selector. Furthermore, Capsule will automatically inject the required labels to avoid a `TenantResource` could start polluting other Namespaces. + +Eventually, using the key `namespacedItem`, it is possible to reference existing objects to get propagated across the other Tenant namespaces: in this case, a Tenant Owner can just refer to objects in their Namespaces, preventing a possible escalation referring to non owned objects. + +As with `GlobalTenantResource`, the full reference of the API is available in the [CRDs API section](/docs/general/crds-apis). + --- This ends our tutorial on how to implement complex multi-tenancy and policy-driven scenarios with Capsule. As we improve it, more use cases about multi-tenancy, policy admission control, and cluster governance will be covered in the future. diff --git a/docs/gridsome.server.js b/docs/gridsome.server.js index f85a7649..a4d02b80 100644 --- a/docs/gridsome.server.js +++ b/docs/gridsome.server.js @@ -39,7 +39,7 @@ module.exports = function (api) { }, { label: 'CRDs APIs', - path: '/docs/general/tenant-crd' + path: '/docs/general/crds-apis' }, { label: 'Multi-Tenant Benchmark', From 6403b6059011e0950357032c1658126ddab8f42c Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 28 Oct 2022 17:57:48 -0400 Subject: [PATCH 027/153] refactor(test): generating namespace names avoiding collision --- e2e/allowed_external_ips_test.go | 6 +++--- e2e/container_registry_test.go | 10 +++++----- e2e/custom_capsule_group_test.go | 6 +++--- e2e/disable_externalname_test.go | 2 +- e2e/disable_ingress_wildcard_test.go | 6 +++--- e2e/disable_loadbalancer_test.go | 2 +- e2e/disable_node_ports_test.go | 2 +- e2e/enable_loadbalancer_test.go | 2 +- e2e/enable_node_ports_test.go | 2 +- e2e/imagepullpolicy_multiple_test.go | 2 +- e2e/imagepullpolicy_single_test.go | 2 +- e2e/ingress_class_extensions_test.go | 10 +++++----- e2e/ingress_class_networking_test.go | 10 +++++----- ...gress_hostnames_collision_cluster_scope_test.go | 4 ++-- e2e/ingress_hostnames_collision_disabled_test.go | 4 ++-- ...ess_hostnames_collision_namespace_scope_test.go | 4 ++-- ...ngress_hostnames_collision_tenant_scope_test.go | 4 ++-- e2e/ingress_hostnames_test.go | 12 ++++++------ e2e/missing_tenant_test.go | 2 +- e2e/namespace_additional_metadata_test.go | 2 +- e2e/namespace_capsule_label_test.go | 6 +++--- e2e/namespace_user_metadata_test.go | 12 ++++++------ e2e/new_namespace_test.go | 7 ++++--- e2e/overquota_namespace_test.go | 2 +- e2e/owner_webhooks_test.go | 14 +++++++------- e2e/pod_priority_class_test.go | 6 +++--- e2e/selecting_non_owned_tenant_test.go | 2 +- e2e/selecting_tenant_fail_test.go | 2 +- e2e/selecting_tenant_with_label_test.go | 2 +- e2e/service_metadata_test.go | 6 +++--- e2e/storage_class_test.go | 4 ++-- e2e/tenant_cordoning_test.go | 2 +- e2e/utils_test.go | 8 +++++++- 33 files changed, 87 insertions(+), 80 deletions(-) diff --git a/e2e/allowed_external_ips_test.go b/e2e/allowed_external_ips_test.go index 038c1b17..0842e080 100644 --- a/e2e/allowed_external_ips_test.go +++ b/e2e/allowed_external_ips_test.go @@ -52,7 +52,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() { }) It("should fail creating an evil service", func() { - ns := NewNamespace("evil-service") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ @@ -85,7 +85,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() { }) It("should allow the first CIDR block", func() { - ns := NewNamespace("allowed-service-cidr") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ @@ -118,7 +118,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", func() { }) It("should allow the /32 CIDR block", func() { - ns := NewNamespace("allowed-service-strict") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ diff --git a/e2e/container_registry_test.go b/e2e/container_registry_test.go index ef854d06..92d8ec67 100644 --- a/e2e/container_registry_test.go +++ b/e2e/container_registry_test.go @@ -48,7 +48,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should add labels to Namespace", func() { - ns := NewNamespace("registry-labels") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) Eventually(func() (ok bool) { Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.Name}, ns)).Should(Succeed()) @@ -65,7 +65,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should deny running a gcr.io container", func() { - ns := NewNamespace("registry-deny") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ @@ -87,7 +87,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should allow using a registry only match", func() { - ns := NewNamespace("registry-only") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ @@ -112,7 +112,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should allow using an exact match", func() { - ns := NewNamespace("registry-list") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ @@ -137,7 +137,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should allow using a regex match", func() { - ns := NewNamespace("registry-regex") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ diff --git a/e2e/custom_capsule_group_test.go b/e2e/custom_capsule_group_test.go index 64a20807..531c97d6 100644 --- a/e2e/custom_capsule_group_test.go +++ b/e2e/custom_capsule_group_test.go @@ -47,7 +47,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro configuration.Spec.UserGroups = []string{"test"} }) - ns := NewNamespace("cg-namespace-fail") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) @@ -56,7 +56,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro configuration.Spec.UserGroups = []string{"test", "alice"} }) - ns := NewNamespace("cg-namespace-1") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -67,7 +67,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro configuration.Spec.UserGroups = []string{"capsule.clastix.io"} }) - ns := NewNamespace("cg-namespace-2") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) diff --git a/e2e/disable_externalname_test.go b/e2e/disable_externalname_test.go index 855455d4..9d3a1e87 100644 --- a/e2e/disable_externalname_test.go +++ b/e2e/disable_externalname_test.go @@ -51,7 +51,7 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan }) It("should fail creating a service with ExternalService type", func() { - ns := NewNamespace("disable-external-service") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) EventuallyCreation(func() error { diff --git a/e2e/disable_ingress_wildcard_test.go b/e2e/disable_ingress_wildcard_test.go index 4e848e99..901a39d7 100644 --- a/e2e/disable_ingress_wildcard_test.go +++ b/e2e/disable_ingress_wildcard_test.go @@ -60,7 +60,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the } } - ns := NewNamespace("extensions-v1beta1") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -141,7 +141,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the } } - ns := NewNamespace("networking-v1beta1") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -222,7 +222,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the } } - ns := NewNamespace("networking-v1") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/disable_loadbalancer_test.go b/e2e/disable_loadbalancer_test.go index c532a180..d94983b7 100644 --- a/e2e/disable_loadbalancer_test.go +++ b/e2e/disable_loadbalancer_test.go @@ -50,7 +50,7 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant }) It("should fail creating a service with LoadBalancer type", func() { - ns := NewNamespace("disable-loadbalancer-service") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/disable_node_ports_test.go b/e2e/disable_node_ports_test.go index c384f453..8e5cb2d9 100644 --- a/e2e/disable_node_ports_test.go +++ b/e2e/disable_node_ports_test.go @@ -50,7 +50,7 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", f }) It("should fail creating a service with NodePort type", func() { - ns := NewNamespace("disable-node-ports") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ diff --git a/e2e/enable_loadbalancer_test.go b/e2e/enable_loadbalancer_test.go index 32a45e85..e6201cc0 100644 --- a/e2e/enable_loadbalancer_test.go +++ b/e2e/enable_loadbalancer_test.go @@ -50,7 +50,7 @@ var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant" }) It("should succeed creating a service with LoadBalancer type", func() { - ns := NewNamespace("enable-loadbalancer-service") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/enable_node_ports_test.go b/e2e/enable_node_ports_test.go index b0a27ba7..b8803f42 100644 --- a/e2e/enable_node_ports_test.go +++ b/e2e/enable_node_ports_test.go @@ -43,7 +43,7 @@ var _ = Describe("creating a nodePort service when it is enabled for Tenant", fu }) It("should allow creating a service with NodePort type", func() { - ns := NewNamespace("enable-node-ports") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ diff --git a/e2e/imagepullpolicy_multiple_test.go b/e2e/imagepullpolicy_multiple_test.go index f973e69c..d725c4f6 100644 --- a/e2e/imagepullpolicy_multiple_test.go +++ b/e2e/imagepullpolicy_multiple_test.go @@ -45,7 +45,7 @@ var _ = Describe("enforcing some defined ImagePullPolicy", func() { }) It("should just allow the defined policies", func() { - ns := NewNamespace("allow-policy") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) cs := ownerClient(tnt.Spec.Owners[0]) diff --git a/e2e/imagepullpolicy_single_test.go b/e2e/imagepullpolicy_single_test.go index 41918ad0..9b99e349 100644 --- a/e2e/imagepullpolicy_single_test.go +++ b/e2e/imagepullpolicy_single_test.go @@ -45,7 +45,7 @@ var _ = Describe("enforcing a defined ImagePullPolicy", func() { }) It("should just allow the defined policy", func() { - ns := NewNamespace("allow-policies") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) cs := ownerClient(tnt.Spec.Owners[0]) diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index f5f278b3..deb949c2 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -57,7 +57,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }) It("should block a non allowed class for extensions/v1beta1", func() { - ns := NewNamespace("ingress-class-disallowed-extensions-v1beta1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -142,7 +142,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }) It("should allow enabled class using the deprecated annotation", func() { - ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -189,7 +189,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", Skip("Running test on Kubernetes " + version.String() + ", doesn't provide .spec.ingressClassName") } - ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -216,7 +216,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }) It("should allow enabled Ingress by regex using the deprecated annotation", func() { - ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) ingressClass := "oil-ingress" @@ -251,7 +251,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }) It("should allow enabled Ingress by regex using the ingressClassName field", func() { - ns := NewNamespace("ingress-class-allowed-annotation-extensions-v1beta1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) ingressClass := "oil-haproxy" diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index 22459441..14a70227 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -63,7 +63,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } } - ns := NewNamespace("ingress-class-disallowed-networking-v1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -146,7 +146,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } } - ns := NewNamespace("ingress-class-allowed-annotation-networking-v1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -186,7 +186,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } } - ns := NewNamespace("ingress-class-allowed-annotation-networking-v1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -224,7 +224,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } } - ns := NewNamespace("ingress-class-allowed-annotation-networking-v1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) ingressClass := "oil-ingress" @@ -263,7 +263,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } } - ns := NewNamespace("ingress-class-allowed-annotation-networking-v1") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) ingressClass := "oil-haproxy" diff --git a/e2e/ingress_hostnames_collision_cluster_scope_test.go b/e2e/ingress_hostnames_collision_cluster_scope_test.go index 34ad3bb9..de503367 100644 --- a/e2e/ingress_hostnames_collision_cluster_scope_test.go +++ b/e2e/ingress_hostnames_collision_cluster_scope_test.go @@ -144,12 +144,12 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", fun }) It("should ensure Cluster scope for Ingress hostname and path collision", func() { - ns1 := NewNamespace("tenant-one-ns") + ns1 := NewNamespace("") cs1 := ownerClient(tnt1.Spec.Owners[0]) NamespaceCreation(ns1, tnt1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt1, defaultTimeoutInterval).Should(ContainElement(ns1.GetName())) - ns2 := NewNamespace("tenant-two-ns") + ns2 := NewNamespace("") cs2 := ownerClient(tnt2.Spec.Owners[0]) NamespaceCreation(ns2, tnt2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt2, defaultTimeoutInterval).Should(ContainElement(ns2.GetName())) diff --git a/e2e/ingress_hostnames_collision_disabled_test.go b/e2e/ingress_hostnames_collision_disabled_test.go index f63cdc5a..081dfc9c 100644 --- a/e2e/ingress_hostnames_collision_disabled_test.go +++ b/e2e/ingress_hostnames_collision_disabled_test.go @@ -120,8 +120,8 @@ var _ = Describe("when disabling Ingress hostnames collision", func() { }) It("should not check any kind of collision", func() { - ns1 := NewNamespace("namespace-collision-one") - ns2 := NewNamespace("namespace-collision-two") + ns1 := NewNamespace("") + ns2 := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/ingress_hostnames_collision_namespace_scope_test.go b/e2e/ingress_hostnames_collision_namespace_scope_test.go index ecda0d38..bfc43b95 100644 --- a/e2e/ingress_hostnames_collision_namespace_scope_test.go +++ b/e2e/ingress_hostnames_collision_namespace_scope_test.go @@ -120,8 +120,8 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", f }) It("should ensure Namespace scope for Ingress hostname and path collision", func() { - ns1 := NewNamespace("namespace-collision-one") - ns2 := NewNamespace("namespace-collision-two") + ns1 := NewNamespace("") + ns2 := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/ingress_hostnames_collision_tenant_scope_test.go b/e2e/ingress_hostnames_collision_tenant_scope_test.go index 55385be1..6e3b67b9 100644 --- a/e2e/ingress_hostnames_collision_tenant_scope_test.go +++ b/e2e/ingress_hostnames_collision_tenant_scope_test.go @@ -120,9 +120,9 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func It("should ensure Tenant scope for Ingress hostname and path collision", func() { - ns1 := NewNamespace("cluster-collision-one") + ns1 := NewNamespace("") - ns2 := NewNamespace("cluster-collision-two") + ns2 := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) diff --git a/e2e/ingress_hostnames_test.go b/e2e/ingress_hostnames_test.go index d0f55640..0c12fd80 100644 --- a/e2e/ingress_hostnames_test.go +++ b/e2e/ingress_hostnames_test.go @@ -121,7 +121,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should block a non allowed Hostname", func() { - ns := NewNamespace("disallowed-hostname-networking") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -144,7 +144,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should block a non allowed Hostname", func() { - ns := NewNamespace("disallowed-hostname-extensions") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -167,7 +167,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should allow Hostnames in list", func() { - ns := NewNamespace("allowed-hostname-list-networking") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -192,7 +192,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should allow Hostnames in list", func() { - ns := NewNamespace("allowed-hostname-list-extensions") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -217,7 +217,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should allow Hostnames in regex", func() { - ns := NewNamespace("allowed-hostname-regex-networking") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) @@ -242,7 +242,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { }) It("should allow Hostnames in regex", func() { - ns := NewNamespace("allowed-hostname-regex-extensions") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/missing_tenant_test.go b/e2e/missing_tenant_test.go index dd78b24d..1ff51b66 100644 --- a/e2e/missing_tenant_test.go +++ b/e2e/missing_tenant_test.go @@ -27,7 +27,7 @@ var _ = Describe("creating a Namespace creation with no Tenant assigned", func() }, }, } - ns := NewNamespace("no-namespace") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) _, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) Expect(err).ShouldNot(Succeed()) diff --git a/e2e/namespace_additional_metadata_test.go b/e2e/namespace_additional_metadata_test.go index 3bded7d2..68452022 100644 --- a/e2e/namespace_additional_metadata_test.go +++ b/e2e/namespace_additional_metadata_test.go @@ -54,7 +54,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f }) It("should contain additional Namespace metadata", func() { - ns := NewNamespace("namespace-metadata") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) diff --git a/e2e/namespace_capsule_label_test.go b/e2e/namespace_capsule_label_test.go index 49777ca3..bb223550 100644 --- a/e2e/namespace_capsule_label_test.go +++ b/e2e/namespace_capsule_label_test.go @@ -44,9 +44,9 @@ var _ = Describe("creating several Namespaces for a Tenant", func() { It("should contains the default Capsule label", func() { namespaces := []*v1.Namespace{ - NewNamespace("first-capsule-ns"), - NewNamespace("second-capsule-ns"), - NewNamespace("third-capsule-ns"), + NewNamespace(""), + NewNamespace(""), + NewNamespace(""), } for _, ns := range namespaces { NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/namespace_user_metadata_test.go b/e2e/namespace_user_metadata_test.go index 55243f79..298b5a18 100644 --- a/e2e/namespace_user_metadata_test.go +++ b/e2e/namespace_user_metadata_test.go @@ -51,12 +51,12 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation It("should allow", func() { By("specifying non-forbidden labels", func() { - ns := NewNamespace("namespace-user-metadata-allowed-labels") + ns := NewNamespace("") ns.SetLabels(map[string]string{"bim": "baz"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) }) By("specifying non-forbidden annotations", func() { - ns := NewNamespace("namespace-user-metadata-allowed-annotations") + ns := NewNamespace("") ns.SetAnnotations(map[string]string{"bim": "baz"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) }) @@ -64,22 +64,22 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation It("should fail when creating a Namespace", func() { By("specifying forbidden labels using exact match", func() { - ns := NewNamespace("namespace-user-metadata-forbidden-labels") + ns := NewNamespace("") ns.SetLabels(map[string]string{"foo": "bar"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden labels using regex match", func() { - ns := NewNamespace("namespace-user-metadata-forbidden-labels") + ns := NewNamespace("") ns.SetLabels(map[string]string{"gatsby-foo": "bar"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden annotations using exact match", func() { - ns := NewNamespace("namespace-user-metadata-forbidden-labels") + ns := NewNamespace("") ns.SetAnnotations(map[string]string{"foo": "bar"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden annotations using regex match", func() { - ns := NewNamespace("namespace-user-metadata-forbidden-labels") + ns := NewNamespace("") ns.SetAnnotations(map[string]string{"gatsby-foo": "bar"}) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) }) diff --git a/e2e/new_namespace_test.go b/e2e/new_namespace_test.go index 440821b4..a14e6c04 100644 --- a/e2e/new_namespace_test.go +++ b/e2e/new_namespace_test.go @@ -7,6 +7,7 @@ package e2e import ( "context" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,7 +49,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun }) It("should be available in Tenant namespaces list and RoleBindings should be present when created", func() { - ns := NewNamespace("new-namespace-user") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) @@ -57,7 +58,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun } }) It("should be available in Tenant namespaces list and RoleBindings should present when created as Group", func() { - ns := NewNamespace("new-namespace-group") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[1], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) @@ -66,7 +67,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", fun } }) It("should be available in Tenant namespaces list and RoleBindings should present when created as ServiceAccount", func() { - ns := NewNamespace("new-namespace-sa") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[2], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) diff --git a/e2e/overquota_namespace_test.go b/e2e/overquota_namespace_test.go index aec9e7e7..986c55c5 100644 --- a/e2e/overquota_namespace_test.go +++ b/e2e/overquota_namespace_test.go @@ -52,7 +52,7 @@ var _ = Describe("creating a Namespace in over-quota of three", func() { } }) - ns := NewNamespace("bob-fail") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) _, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) Expect(err).ShouldNot(Succeed()) diff --git a/e2e/owner_webhooks_test.go b/e2e/owner_webhooks_test.go index 1e9a4335..63a8fecc 100644 --- a/e2e/owner_webhooks_test.go +++ b/e2e/owner_webhooks_test.go @@ -100,7 +100,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { It("should disallow deletions", func() { By("blocking Capsule Limit ranges", func() { - ns := NewNamespace("limit-range-disallow") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -114,7 +114,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { Expect(cs.CoreV1().LimitRanges(ns.GetName()).Delete(context.TODO(), lr.Name, metav1.DeleteOptions{})).ShouldNot(Succeed()) }) By("blocking Capsule Network Policy", func() { - ns := NewNamespace("network-policy-disallow") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -128,7 +128,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { Expect(cs.NetworkingV1().NetworkPolicies(ns.GetName()).Delete(context.TODO(), np.Name, metav1.DeleteOptions{})).ShouldNot(Succeed()) }) By("blocking Capsule Resource Quota", func() { - ns := NewNamespace("resource-quota-disallow") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -145,7 +145,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { It("should allow", func() { By("listing Limit Range", func() { - ns := NewNamespace("limit-range-list") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -156,7 +156,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) By("listing Network Policy", func() { - ns := NewNamespace("network-policy-list") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -167,7 +167,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) By("listing Resource Quota", func() { - ns := NewNamespace("resource-quota-list") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -180,7 +180,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { }) It("should allow all actions to Tenant owner Network Policy", func() { - ns := NewNamespace("network-policy-allow") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index fc7dcdf4..852ec00b 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -48,7 +48,7 @@ var _ = Describe("enforcing a Priority Class", func() { }) It("should block non allowed Priority Class", func() { - ns := NewNamespace("system-node-critical") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ @@ -87,7 +87,7 @@ var _ = Describe("enforcing a Priority Class", func() { Expect(k8sClient.Delete(context.TODO(), pc)).Should(Succeed()) }() - ns := NewNamespace("pc-exact-match") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ @@ -113,7 +113,7 @@ var _ = Describe("enforcing a Priority Class", func() { }) It("should allow regex match", func() { - ns := NewNamespace("pc-regex-match") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/selecting_non_owned_tenant_test.go b/e2e/selecting_non_owned_tenant_test.go index f1a47828..82ec38f3 100644 --- a/e2e/selecting_non_owned_tenant_test.go +++ b/e2e/selecting_non_owned_tenant_test.go @@ -48,7 +48,7 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", func() l, err := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) Expect(err).ToNot(HaveOccurred()) - ns := NewNamespace("tenant-non-owned-ns") + ns := NewNamespace("") ns.SetLabels(map[string]string{ l: tnt.Name, }) diff --git a/e2e/selecting_tenant_fail_test.go b/e2e/selecting_tenant_fail_test.go index bfa4150f..960a98ec 100644 --- a/e2e/selecting_tenant_fail_test.go +++ b/e2e/selecting_tenant_fail_test.go @@ -70,7 +70,7 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns } It("should fail", func() { - ns := NewNamespace("fail-ns") + ns := NewNamespace("") By("user owns 2 tenants", func() { EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t1) }).Should(Succeed()) EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t2) }).Should(Succeed()) diff --git a/e2e/selecting_tenant_with_label_test.go b/e2e/selecting_tenant_with_label_test.go index abc359e0..9f1270c7 100644 --- a/e2e/selecting_tenant_with_label_test.go +++ b/e2e/selecting_tenant_with_label_test.go @@ -58,7 +58,7 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi }) It("should be assigned to the selected Tenant", func() { - ns := NewNamespace("tenant-2-ns") + ns := NewNamespace("") By("assigning to the Namespace the Capsule Tenant label", func() { l, err := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) Expect(err).ToNot(HaveOccurred()) diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index 58fa24c8..8ed2557a 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -67,7 +67,7 @@ var _ = Describe("adding metadata to Service objects", func() { }) It("should apply them to Service", func() { - ns := NewNamespace("service-metadata") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -144,7 +144,7 @@ var _ = Describe("adding metadata to Service objects", func() { }) It("should apply them to Endpoints", func() { - ns := NewNamespace("endpoints-metadata") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -228,7 +228,7 @@ var _ = Describe("adding metadata to Service objects", func() { } } - ns := NewNamespace("endpointslice-metadata") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go index 8a25a0cc..e5a1c273 100644 --- a/e2e/storage_class_test.go +++ b/e2e/storage_class_test.go @@ -52,7 +52,7 @@ var _ = Describe("when Tenant handles Storage classes", func() { }) It("should fails", func() { - ns := NewNamespace("storage-class-disallowed") + ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) @@ -99,7 +99,7 @@ var _ = Describe("when Tenant handles Storage classes", func() { }) It("should allow", func() { - ns := NewNamespace("storage-class-allowed") + ns := NewNamespace("") cs := ownerClient(tnt.Spec.Owners[0]) NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) diff --git a/e2e/tenant_cordoning_test.go b/e2e/tenant_cordoning_test.go index fa12d391..4ece4159 100644 --- a/e2e/tenant_cordoning_test.go +++ b/e2e/tenant_cordoning_test.go @@ -46,7 +46,7 @@ var _ = Describe("cordoning a Tenant", func() { It("should block or allow operations", func() { cs := ownerClient(tnt.Spec.Owners[0]) - ns := NewNamespace("cordoned-namespace") + ns := NewNamespace("") pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ diff --git a/e2e/utils_test.go b/e2e/utils_test.go index 56eb9052..ad0cc1ba 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -8,10 +8,12 @@ package e2e import ( "context" "fmt" - "sigs.k8s.io/controller-runtime/pkg/client" "strings" "time" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -31,6 +33,10 @@ const ( ) func NewNamespace(name string) *corev1.Namespace { + if len(name) == 0 { + name = rand.String(10) + } + return &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: name, From b1ec9fed50a4979bce7391a372c685ec5129fc71 Mon Sep 17 00:00:00 2001 From: Max Fedotov Date: Thu, 27 Oct 2022 22:29:37 +0300 Subject: [PATCH 028/153] feat: refactor resources controller Co-authored-by: Maksim Fedotov --- api/v1beta2/tenantresource_global.go | 14 +- api/v1beta2/tenantresource_namespaced.go | 2 +- api/v1beta2/zz_generated.deepcopy.go | 23 +- controllers/resources/global.go | 89 ++++-- controllers/resources/namespaced.go | 114 ++++--- controllers/resources/processor.go | 263 +++++++++++++++- controllers/resources/processor_finalizer.go | 54 ---- controllers/resources/processor_pruning.go | 67 ---- controllers/resources/processor_section.go | 223 ------------- go.mod | 81 +++-- go.sum | 311 ++++++++++--------- main.go | 24 ++ 12 files changed, 660 insertions(+), 605 deletions(-) delete mode 100644 controllers/resources/processor_finalizer.go delete mode 100644 controllers/resources/processor_pruning.go delete mode 100644 controllers/resources/processor_section.go diff --git a/api/v1beta2/tenantresource_global.go b/api/v1beta2/tenantresource_global.go index 537b2caa..fd918fa6 100644 --- a/api/v1beta2/tenantresource_global.go +++ b/api/v1beta2/tenantresource_global.go @@ -5,6 +5,7 @@ package v1beta2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" ) // GlobalTenantResourceSpec defines the desired state of GlobalTenantResource. @@ -19,7 +20,18 @@ type GlobalTenantResourceStatus struct { // List of Tenants addressed by the GlobalTenantResource. SelectedTenants []string `json:"selectedTenants"` // List of the replicated resources for the given TenantResource. - ProcessedItems []ObjectReferenceStatus `json:"processedItems"` + ProcessedItems ProcessedItems `json:"processedItems"` +} + +type ProcessedItems []ObjectReferenceStatus + +func (p *ProcessedItems) AsSet() sets.String { + set := sets.NewString() + for _, i := range *p { + set.Insert(i.String()) + } + + return set } //+kubebuilder:object:root=true diff --git a/api/v1beta2/tenantresource_namespaced.go b/api/v1beta2/tenantresource_namespaced.go index 9a0d18d6..3d19a952 100644 --- a/api/v1beta2/tenantresource_namespaced.go +++ b/api/v1beta2/tenantresource_namespaced.go @@ -46,7 +46,7 @@ type RawExtension struct { // TenantResourceStatus defines the observed state of TenantResource. type TenantResourceStatus struct { // List of the replicated resources for the given TenantResource. - ProcessedItems []ObjectReferenceStatus `json:"processedItems"` + ProcessedItems ProcessedItems `json:"processedItems"` } //+kubebuilder:object:root=true diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index b6c0df5d..06023ba2 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -241,7 +241,7 @@ func (in *GlobalTenantResourceStatus) DeepCopyInto(out *GlobalTenantResourceStat } if in.ProcessedItems != nil { in, out := &in.ProcessedItems, &out.ProcessedItems - *out = make([]ObjectReferenceStatus, len(*in)) + *out = make(ProcessedItems, len(*in)) copy(*out, *in) } } @@ -436,6 +436,25 @@ func (in *OwnerSpec) DeepCopy() *OwnerSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in ProcessedItems) DeepCopyInto(out *ProcessedItems) { + { + in := &in + *out = make(ProcessedItems, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProcessedItems. +func (in ProcessedItems) DeepCopy() ProcessedItems { + if in == nil { + return nil + } + out := new(ProcessedItems) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxySettings) DeepCopyInto(out *ProxySettings) { *out = *in @@ -662,7 +681,7 @@ func (in *TenantResourceStatus) DeepCopyInto(out *TenantResourceStatus) { *out = *in if in.ProcessedItems != nil { in, out := &in.ProcessedItems, &out.ProcessedItems - *out = make([]ObjectReferenceStatus, len(*in)) + *out = make(ProcessedItems, len(*in)) copy(*out, *in) } } diff --git a/controllers/resources/global.go b/controllers/resources/global.go index be485319..673cc367 100644 --- a/controllers/resources/global.go +++ b/controllers/resources/global.go @@ -7,13 +7,16 @@ import ( "context" "github.com/hashicorp/go-multierror" - "k8s.io/apimachinery/pkg/api/errors" + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -60,21 +63,9 @@ func (r *Global) enqueueRequestFromTenant(object client.Object) (reqs []reconcil } func (r *Global) SetupWithManager(mgr ctrl.Manager) error { - unstructuredCachingClient, err := client.NewDelegatingClient( - client.NewDelegatingClientInput{ - Client: mgr.GetClient(), - CacheReader: mgr.GetCache(), - CacheUnstructured: true, - }, - ) - if err != nil { - return err - } - r.client = mgr.GetClient() r.processor = Processor{ - client: r.client, - unstructuredClient: unstructuredCachingClient, + client: mgr.GetClient(), } return ctrl.NewControllerManagedBy(mgr). @@ -83,14 +74,15 @@ func (r *Global) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } +//nolint:dupl func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { log := ctrllog.FromContext(ctx) log.Info("start processing") - - tntResource := capsulev1beta2.GlobalTenantResource{} - if err := r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { - if errors.IsNotFound(err) { + // Retrieving the GlobalTenantResource + tntResource := &capsulev1beta2.GlobalTenantResource{} + if err := r.client.Get(ctx, request.NamespacedName, tntResource); err != nil { + if apierrors.IsNotFound(err) { log.Info("Request object not found, could have been deleted after reconcile request") return reconcile.Result{}, nil @@ -98,15 +90,40 @@ func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reco return reconcile.Result{}, err } - // Adding the default value for the status + + patchHelper, err := patch.NewHelper(tntResource, r.client) + if err != nil { + return reconcile.Result{}, errors.Wrap(err, "failed to init patch helper") + } + + defer func() { + if e := patchHelper.Patch(ctx, tntResource); e != nil { + if err == nil { + err = errors.Wrap(e, "failed to patch GlobalTenantResource") + } + } + }() + + // Handle deleted GlobalTenantResource + if !tntResource.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, tntResource) + } + + // Handle non-deleted GlobalTenantResource + return r.reconcileNormal(ctx, tntResource) +} + +func (r *Global) reconcileNormal(ctx context.Context, tntResource *capsulev1beta2.GlobalTenantResource) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + if *tntResource.Spec.PruningOnDelete { + controllerutil.AddFinalizer(tntResource, finalizer) + } + if tntResource.Status.ProcessedItems == nil { tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, 0) } - // Handling the finalizer section for the given GlobalTenantResource - enqueueBack, err := r.processor.HandleFinalizer(ctx, &tntResource, *tntResource.Spec.PruningOnDelete, tntResource.Status.ProcessedItems) - if err != nil || enqueueBack { - return reconcile.Result{}, err - } + // Retrieving the list of the Tenants up to the selector provided by the GlobalTenantResource resource. tntSelector, err := metav1.LabelSelectorAsSelector(&tntResource.Spec.TenantSelector) if err != nil { @@ -158,9 +175,7 @@ func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reco return reconcile.Result{}, err } - shouldUpdateStatus := !sets.NewString(tntResource.Status.SelectedTenants...).Equal(tntSet) - - if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems, processedItems) { + if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), processedItems) { tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) for _, item := range processedItems.List() { @@ -168,16 +183,22 @@ func (r *Global) Reconcile(ctx context.Context, request reconcile.Request) (reco tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or) } } - - shouldUpdateStatus = true } - if shouldUpdateStatus { - tntResource.Status.SelectedTenants = tntSet.List() + tntResource.Status.SelectedTenants = tntSet.List() - if updateErr := r.client.Status().Update(ctx, &tntResource); updateErr != nil { - log.Error(updateErr, "unable to update TenantResource status") - } + log.Info("processing completed") + + return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil +} + +func (r *Global) reconcileDelete(ctx context.Context, tntResource *capsulev1beta2.GlobalTenantResource) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + if *tntResource.Spec.PruningOnDelete { + r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), nil) + + controllerutil.RemoveFinalizer(tntResource, finalizer) } log.Info("processing completed") diff --git a/controllers/resources/namespaced.go b/controllers/resources/namespaced.go index 891d3a3d..92a2d1ca 100644 --- a/controllers/resources/namespaced.go +++ b/controllers/resources/namespaced.go @@ -7,12 +7,14 @@ import ( "context" "github.com/hashicorp/go-multierror" - apierr "k8s.io/apimachinery/pkg/api/errors" + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/client-go/util/retry" + "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -21,25 +23,13 @@ import ( type Namespaced struct { client client.Client - finalizer Processor + processor Processor } func (r *Namespaced) SetupWithManager(mgr ctrl.Manager) error { - unstructuredCachingClient, err := client.NewDelegatingClient( - client.NewDelegatingClientInput{ - Client: mgr.GetClient(), - CacheReader: mgr.GetCache(), - CacheUnstructured: true, - }, - ) - if err != nil { - return err - } - r.client = mgr.GetClient() - r.finalizer = Processor{ - client: r.client, - unstructuredClient: unstructuredCachingClient, + r.processor = Processor{ + client: mgr.GetClient(), } return ctrl.NewControllerManagedBy(mgr). @@ -47,37 +37,62 @@ func (r *Namespaced) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } +//nolint:dupl func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { log := ctrllog.FromContext(ctx) log.Info("start processing") // Retrieving the TenantResource - tntResource := capsulev1beta2.TenantResource{} - if err := r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { - if apierr.IsNotFound(err) { + tntResource := &capsulev1beta2.TenantResource{} + if err := r.client.Get(ctx, request.NamespacedName, tntResource); err != nil { + if apierrors.IsNotFound(err) { log.Info("Request object not found, could have been deleted after reconcile request") return reconcile.Result{}, nil } - log.Error(err, "cannot retrieve capsulev1beta2.TenantResource") - return reconcile.Result{}, err } + + patchHelper, err := patch.NewHelper(tntResource, r.client) + if err != nil { + return reconcile.Result{}, errors.Wrap(err, "failed to init patch helper") + } + + defer func() { + if e := patchHelper.Patch(ctx, tntResource); e != nil { + if err == nil { + err = errors.Wrap(e, "failed to patch TenantResource") + } + } + }() + + // Handle deleted TenantResource + if !tntResource.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, tntResource) + } + + // Handle non-deleted TenantResource + return r.reconcileNormal(ctx, tntResource) +} + +func (r *Namespaced) reconcileNormal(ctx context.Context, tntResource *capsulev1beta2.TenantResource) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + if *tntResource.Spec.PruningOnDelete { + controllerutil.AddFinalizer(tntResource, finalizer) + } + // Adding the default value for the status if tntResource.Status.ProcessedItems == nil { tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, 0) } - // Handling the finalizer section for the given TenantResource - enqueueBack, err := r.finalizer.HandleFinalizer(ctx, &tntResource, *tntResource.Spec.PruningOnDelete, tntResource.Status.ProcessedItems) - if err != nil || enqueueBack { - return reconcile.Result{}, err - } - // Retrieving the parent of the Global Resource: + + // Retrieving the parent of the Tenant Resource: // can be owned, or being deployed in one of its Namespace. tl := &capsulev1beta2.TenantList{} - if err = r.client.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", tntResource.GetNamespace())}); err != nil { - log.Error(err, "unable to detect the Global for the given TenantResource") + if err := r.client.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", tntResource.GetNamespace())}); err != nil { + log.Error(err, "unable to detect the Tenant for the given TenantResource") return reconcile.Result{}, err } @@ -88,7 +103,7 @@ func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) ( return reconcile.Result{}, nil } - err = new(multierror.Error) + err := new(multierror.Error) // A TenantResource is made of several Resource sections, each one with specific options: // the Status can be updated only in case of no errors across all of them to guarantee a valid and coherent status. processedItems := sets.NewString() @@ -101,7 +116,7 @@ func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) ( } for index, resource := range tntResource.Spec.Resources { - items, sectionErr := r.finalizer.HandleSection(ctx, tl.Items[0], false, tenantLabel, index, resource) + items, sectionErr := r.processor.HandleSection(ctx, tl.Items[0], false, tenantLabel, index, resource) if sectionErr != nil { // Upon a process error storing the last error occurred and continuing to iterate, // avoid to block the whole processing. @@ -111,33 +126,36 @@ func (r *Namespaced) Reconcile(ctx context.Context, request reconcile.Request) ( } } - if err.(*multierror.Error).ErrorOrNil() != nil { //nolint:errorlint,forcetypeassert + if err.ErrorOrNil() != nil { log.Error(err, "unable to replicate the requested resources") return reconcile.Result{}, err } - if r.finalizer.HandlePruning(ctx, tntResource.Status.ProcessedItems, processedItems) { - statusErr := retry.RetryOnConflict(retry.DefaultRetry, func() (err error) { - if err = r.client.Get(ctx, request.NamespacedName, &tntResource); err != nil { - return err + if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), processedItems) { + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) + + for _, item := range processedItems.List() { + if or := (capsulev1beta2.ObjectReferenceStatus{}); or.ParseFromString(item) == nil { + tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or) } + } + } - tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) + log.Info("processing completed") - for _, item := range processedItems.List() { - if or := (capsulev1beta2.ObjectReferenceStatus{}); or.ParseFromString(item) == nil { - tntResource.Status.ProcessedItems = append(tntResource.Status.ProcessedItems, or) - } - } + return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil +} - return r.client.Status().Update(ctx, &tntResource) - }) - if statusErr != nil { - log.Error(statusErr, "unable to update TenantResource status") - } +func (r *Namespaced) reconcileDelete(ctx context.Context, tntResource *capsulev1beta2.TenantResource) (reconcile.Result, error) { + log := ctrllog.FromContext(ctx) + + if *tntResource.Spec.PruningOnDelete { + r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), nil) } + controllerutil.RemoveFinalizer(tntResource, finalizer) + log.Info("processing completed") return reconcile.Result{Requeue: true, RequeueAfter: tntResource.Spec.ResyncPeriod.Duration}, nil diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go index ed71c665..841fee19 100644 --- a/controllers/resources/processor.go +++ b/controllers/resources/processor.go @@ -4,7 +4,24 @@ package resources import ( + "context" + "fmt" + + "github.com/hashicorp/go-multierror" + corev1 "k8s.io/api/core/v1" + apierr "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) const ( @@ -12,6 +29,248 @@ const ( ) type Processor struct { - client client.Client - unstructuredClient client.Client + client client.Client +} + +func (r *Processor) HandlePruning(ctx context.Context, current, desired sets.String) (updateStatus bool) { + log := ctrllog.FromContext(ctx) + + diff := current.Difference(desired) + // We don't want to trigger a reconciliation of the Status every time, + // rather, only in case of a difference between the processed and the actual status. + // This can happen upon the first reconciliation, or a removal, or a change, of a resource. + updateStatus = diff.Len() > 0 || current.Len() != desired.Len() + + if diff.Len() > 0 { + log.Info("starting processing pruning", "length", diff.Len()) + } + + // The outer resources must be removed, iterating over these to clean-up + for item := range diff { + or := capsulev1beta2.ObjectReferenceStatus{} + if err := or.ParseFromString(item); err != nil { + log.Error(err, "unable to parse resource to prune", "resource", item) + + continue + } + + obj := unstructured.Unstructured{} + obj.SetNamespace(or.Namespace) + obj.SetName(or.Name) + obj.SetGroupVersionKind(schema.FromAPIVersionAndKind(or.APIVersion, or.Kind)) + + if err := r.client.Delete(ctx, &obj); err != nil { + if apierr.IsNotFound(err) { + // Object may have been already deleted, we can ignore this error + continue + } + + log.Error(err, "unable to prune resource", "resource", item) + + continue + } + + log.Info("resource has been pruned", "resource", item) + } + + return updateStatus +} + +func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant, allowCrossNamespaceSelection bool, tenantLabel string, resourceIndex int, spec capsulev1beta2.ResourceSpec) ([]string, error) { + log := ctrllog.FromContext(ctx) + + var err error + // Creating Namespace selector + var selector labels.Selector + + if spec.NamespaceSelector != nil { + selector, err = metav1.LabelSelectorAsSelector(spec.NamespaceSelector) + if err != nil { + log.Error(err, "cannot create Namespace selector for Namespace filtering and resource replication", "index", resourceIndex) + + return nil, err + } + } else { + selector = labels.NewSelector() + } + // Resources can be replicated only on Namespaces belonging to the same Global: + // preventing a boundary cross by enforcing the selection. + tntRequirement, err := labels.NewRequirement(tenantLabel, selection.Equals, []string{tnt.GetName()}) + if err != nil { + log.Error(err, "unable to create requirement for Namespace filtering and resource replication", "index", resourceIndex) + + return nil, err + } + + selector = selector.Add(*tntRequirement) + // Selecting the targeted Namespace according to the TenantResource specification. + namespaces := corev1.NamespaceList{} + if err = r.client.List(ctx, &namespaces, client.MatchingLabelsSelector{Selector: selector}); err != nil { + log.Error(err, "cannot retrieve Namespaces for resource", "index", resourceIndex) + + return nil, err + } + // Generating additional metadata + objAnnotations, objLabels := map[string]string{}, map[string]string{} + + if spec.AdditionalMetadata != nil { + objAnnotations = spec.AdditionalMetadata.Annotations + objLabels = spec.AdditionalMetadata.Labels + } + + objAnnotations[tenantLabel] = tnt.GetName() + + objLabels["capsule.clastix.io/resources"] = fmt.Sprintf("%d", resourceIndex) + objLabels[tenantLabel] = tnt.GetName() + // processed will contain the sets of resources replicated, both for the raw and the Namespaced ones: + // these are required to perform a final pruning once the replication has been occurred. + processed := sets.NewString() + + tntNamespaces := sets.NewString(tnt.Status.Namespaces...) + + syncErr := new(multierror.Error) + + for nsIndex, item := range spec.NamespacedItems { + keysAndValues := []interface{}{"index", nsIndex, "namespace", item.Namespace} + // A TenantResource is created by a TenantOwner, and potentially, they could point to a resource in a non-owned + // Namespace: this must be blocked by checking it this is the case. + if !allowCrossNamespaceSelection && !tntNamespaces.Has(item.Namespace) { + log.Info("skipping processing of namespacedItem, referring a Namespace that is not part of the given Global", keysAndValues...) + + continue + } + // Namespaced Items are relying on selecting resources, rather than specifying a specific name: + // creating it to get used by the client List action. + itemSelector, selectorErr := metav1.LabelSelectorAsSelector(&item.Selector) + if err != nil { + log.Error(selectorErr, "cannot create Selector for namespacedItem", keysAndValues...) + + continue + } + + objs := unstructured.UnstructuredList{} + objs.SetGroupVersionKind(schema.FromAPIVersionAndKind(item.APIVersion, fmt.Sprintf("%sList", item.Kind))) + + if clientErr := r.client.List(ctx, &objs, client.InNamespace(item.Namespace), client.MatchingLabelsSelector{Selector: itemSelector}); clientErr != nil { + log.Error(clientErr, "cannot retrieve object for namespacedItem", keysAndValues...) + + syncErr = multierror.Append(syncErr, clientErr) + + continue + } + + multiErr := new(multierror.Group) + // Iterating over all the retrieved objects from the resource spec to get replicated in all the selected Namespaces: + // in case of error during the create or update function, this will be appended to the list of errors. + for _, o := range objs.Items { + obj := o + + multiErr.Go(func() error { + nsItems, nsErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) + if nsErr != nil { + log.Error(err, "unable to sync namespacedItems", keysAndValues...) + + return nsErr + } + + processed.Insert(nsItems...) + + return nil + }) + } + + if objsErr := multiErr.Wait(); objsErr != nil { + syncErr = multierror.Append(syncErr, objsErr) + } + } + + codecFactory := serializer.NewCodecFactory(r.client.Scheme()) + + for rawIndex, item := range spec.RawItems { + obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} + + if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(item.Raw, nil, &obj); decodeErr != nil { + log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) + + syncErr = multierror.Append(syncErr, decodeErr) + + continue + } + + syncedRaw, rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) + if rawErr != nil { + log.Info("unable to sync rawItem", keysAndValues...) + // In case of error processing an item in one of any selected Namespaces, storing it to report it lately + // to the upper call to ensure a partial sync that will be fixed by a subsequent reconciliation. + syncErr = multierror.Append(syncErr, rawErr) + } else { + processed.Insert(syncedRaw...) + } + } + + return processed.List(), syncErr.ErrorOrNil() +} + +// createOrUpdate replicates the provided unstructured object to all the provided Namespaces: +// this function mimics the CreateOrUpdate, by retrieving the object to understand if it must be created or updated, +// along adding the additional metadata, if required. +func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string, annotations map[string]string, namespaces corev1.NamespaceList) ([]string, error) { + log := ctrllog.FromContext(ctx) + + errGroup := new(multierror.Group) + + var items []string + + for _, item := range namespaces.Items { + ns := item.GetName() + + errGroup.Go(func() (err error) { + actual, desired := obj.DeepCopy(), obj.DeepCopy() + // Using a deferred function to properly log the results, and adding the item to the processed set. + defer func() { + keysAndValues := []interface{}{"resource", fmt.Sprintf("%s/%s", ns, desired.GetName())} + + if err != nil { + log.Error(err, "unable to replicate resource", keysAndValues...) + + return + } + + log.Info("resource has been replicated", keysAndValues...) + + replicatedItem := &capsulev1beta2.ObjectReferenceStatus{ + Name: obj.GetName(), + } + replicatedItem.Kind = obj.GetKind() + replicatedItem.Namespace = ns + replicatedItem.APIVersion = obj.GetAPIVersion() + + items = append(items, replicatedItem.String()) + }() + + actual.SetNamespace(ns) + + _, err = controllerutil.CreateOrUpdate(ctx, r.client, actual, func() error { + UID := actual.GetUID() + + actual.SetUnstructuredContent(desired.Object) + actual.SetNamespace(ns) + actual.SetLabels(labels) + actual.SetAnnotations(annotations) + actual.SetResourceVersion("") + actual.SetUID(UID) + + return nil + }) + + return + }) + } + // Wait returns *multierror.Error that implements stdlib error: + // the nil check must be performed down here rather than at the caller level to avoid wrong casting. + if err := errGroup.Wait(); err != nil { + return items, err + } + + return items, nil } diff --git a/controllers/resources/processor_finalizer.go b/controllers/resources/processor_finalizer.go deleted file mode 100644 index edc666bb..00000000 --- a/controllers/resources/processor_finalizer.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package resources - -import ( - "context" - - "k8s.io/apimachinery/pkg/util/sets" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" -) - -func (r *Processor) HandleFinalizer(ctx context.Context, obj client.Object, shouldPrune bool, items []capsulev1beta2.ObjectReferenceStatus) (enqueueBack bool, err error) { - log := ctrllog.FromContext(ctx) - // If the object has been marked for deletion, - // we have to clean up the created resources before removing the finalizer. - if obj.GetDeletionTimestamp() != nil { - log.Info("pruning prior finalizer removal") - - if shouldPrune { - _ = r.HandlePruning(ctx, items, nil) - } - - obj.SetFinalizers(nil) - - if err = r.client.Update(ctx, obj); err != nil { - log.Error(err, "cannot remove finalizer") - - return true, err - } - - return true, nil - } - // When the pruning for the given resource is enabled, a finalizer is required when the TenantResource is marked - // for deletion: this allows to perform a clean-up of all the underlying resources. - if shouldPrune && !sets.NewString(obj.GetFinalizers()...).Has(finalizer) { - obj.SetFinalizers(append(obj.GetFinalizers(), finalizer)) - - if err = r.client.Update(ctx, obj); err != nil { - log.Error(err, "cannot add finalizer") - - return true, err - } - - log.Info("added finalizer, enqueuing back for processing") - - return true, nil - } - - return false, nil -} diff --git a/controllers/resources/processor_pruning.go b/controllers/resources/processor_pruning.go deleted file mode 100644 index a6a37c83..00000000 --- a/controllers/resources/processor_pruning.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package resources - -import ( - "context" - - apierr "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/sets" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" -) - -func (r *Processor) HandlePruning(ctx context.Context, current []capsulev1beta2.ObjectReferenceStatus, desired sets.String) (updateStatus bool) { - log := ctrllog.FromContext(ctx) - // The status items are the actual replicated resources, these must be collected in order to perform the resulting - // diff that will be cleaned-up. - status := sets.NewString() - - for _, item := range current { - status.Insert(item.String()) - } - - diff := status.Difference(desired) - // We don't want to trigger a reconciliation of the Status every time, - // rather, only in case of a difference between the processed and the actual status. - // This can happen upon the first reconciliation, or a removal, or a change, of a resource. - updateStatus = diff.Len() > 0 || status.Len() != desired.Len() - - if diff.Len() > 0 { - log.Info("starting processing pruning", "length", diff.Len()) - } - - // The outer resources must be removed, iterating over these to clean-up - for item := range diff { - or := capsulev1beta2.ObjectReferenceStatus{} - if err := or.ParseFromString(item); err != nil { - log.Error(err, "unable to parse resource to prune", "resource", item) - - continue - } - - obj := unstructured.Unstructured{} - obj.SetNamespace(or.Namespace) - obj.SetName(or.Name) - obj.SetGroupVersionKind(schema.FromAPIVersionAndKind(or.APIVersion, or.Kind)) - - if err := r.unstructuredClient.Delete(ctx, &obj); err != nil { - if apierr.IsNotFound(err) { - // Object may have been already deleted, we can ignore this error - continue - } - - log.Error(err, "unable to prune resource", "resource", item) - - continue - } - - log.Info("resource has been pruned", "resource", item) - } - - return updateStatus -} diff --git a/controllers/resources/processor_section.go b/controllers/resources/processor_section.go deleted file mode 100644 index 77c1b662..00000000 --- a/controllers/resources/processor_section.go +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2020-2021 Clastix Labs -// SPDX-License-Identifier: Apache-2.0 - -package resources - -import ( - "context" - "fmt" - - "github.com/hashicorp/go-multierror" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/selection" - "k8s.io/apimachinery/pkg/util/sets" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" -) - -func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant, allowCrossNamespaceSelection bool, tenantLabel string, resourceIndex int, spec capsulev1beta2.ResourceSpec) ([]string, error) { - log := ctrllog.FromContext(ctx) - - var err error - // Creating Namespace selector - var selector labels.Selector - - if spec.NamespaceSelector != nil { - selector, err = metav1.LabelSelectorAsSelector(spec.NamespaceSelector) - if err != nil { - log.Error(err, "cannot create Namespace selector for Namespace filtering and resource replication", "index", resourceIndex) - - return nil, err - } - } else { - selector = labels.NewSelector() - } - // Resources can be replicated only on Namespaces belonging to the same Global: - // preventing a boundary cross by enforcing the selection. - tntRequirement, err := labels.NewRequirement(tenantLabel, selection.Equals, []string{tnt.GetName()}) - if err != nil { - log.Error(err, "unable to create requirement for Namespace filtering and resource replication", "index", resourceIndex) - - return nil, err - } - - selector = selector.Add(*tntRequirement) - // Selecting the targeted Namespace according to the TenantResource specification. - namespaces := corev1.NamespaceList{} - if err = r.client.List(ctx, &namespaces, client.MatchingLabelsSelector{Selector: selector}); err != nil { - log.Error(err, "cannot retrieve Namespaces for resource", "index", resourceIndex) - - return nil, err - } - // Generating additional metadata - objAnnotations, objLabels := map[string]string{}, map[string]string{} - - if spec.AdditionalMetadata != nil { - objAnnotations = spec.AdditionalMetadata.Annotations - objLabels = spec.AdditionalMetadata.Labels - } - - objAnnotations[tenantLabel] = tnt.GetName() - - objLabels["capsule.clastix.io/resources"] = fmt.Sprintf("%d", resourceIndex) - objLabels[tenantLabel] = tnt.GetName() - // processed will contain the sets of resources replicated, both for the raw and the Namespaced ones: - // these are required to perform a final pruning once the replication has been occurred. - processed := sets.NewString() - - tntNamespaces := sets.NewString(tnt.Status.Namespaces...) - - syncErr := new(multierror.Error) - - for nsIndex, item := range spec.NamespacedItems { - keysAndValues := []interface{}{"index", nsIndex, "namespace", item.Namespace} - // A TenantResource is created by a TenantOwner, and potentially, they could point to a resource in a non-owned - // Namespace: this must be blocked by checking it this is the case. - if !allowCrossNamespaceSelection && !tntNamespaces.Has(item.Namespace) { - log.Info("skipping processing of namespacedItem, referring a Namespace that is not part of the given Global", keysAndValues...) - - continue - } - // Namespaced Items are relying on selecting resources, rather than specifying a specific name: - // creating it to get used by the client List action. - itemSelector, selectorErr := metav1.LabelSelectorAsSelector(&item.Selector) - if err != nil { - log.Error(selectorErr, "cannot create Selector for namespacedItem", keysAndValues...) - - continue - } - - objs := unstructured.UnstructuredList{} - objs.SetGroupVersionKind(schema.FromAPIVersionAndKind(item.APIVersion, fmt.Sprintf("%sList", item.Kind))) - - if clientErr := r.unstructuredClient.List(ctx, &objs, client.InNamespace(item.Namespace), client.MatchingLabelsSelector{Selector: itemSelector}); clientErr != nil { - log.Error(clientErr, "cannot retrieve object for namespacedItem", keysAndValues...) - - syncErr = multierror.Append(syncErr, clientErr) - - continue - } - - multiErr := new(multierror.Group) - // Iterating over all the retrieved objects from the resource spec to get replicated in all the selected Namespaces: - // in case of error during the create or update function, this will be appended to the list of errors. - for _, o := range objs.Items { - obj := o - - multiErr.Go(func() error { - nsItems, nsErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) - if nsErr != nil { - log.Error(err, "unable to sync namespacedItems", keysAndValues...) - - return nsErr - } - - processed.Insert(nsItems...) - - return nil - }) - } - - if objsErr := multiErr.Wait(); objsErr != nil { - syncErr = multierror.Append(syncErr, objsErr) - } - } - - codecFactory := serializer.NewCodecFactory(r.client.Scheme()) - - for rawIndex, item := range spec.RawItems { - obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} - - if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(item.Raw, nil, &obj); decodeErr != nil { - log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) - - syncErr = multierror.Append(syncErr, decodeErr) - - continue - } - - syncedRaw, rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) - if rawErr != nil { - log.Info("unable to sync rawItem", keysAndValues...) - // In case of error processing an item in one of any selected Namespaces, storing it to report it lately - // to the upper call to ensure a partial sync that will be fixed by a subsequent reconciliation. - syncErr = multierror.Append(syncErr, rawErr) - } else { - processed.Insert(syncedRaw...) - } - } - - return processed.List(), syncErr.ErrorOrNil() -} - -// createOrUpdate replicates the provided unstructured object to all the provided Namespaces: -// this function mimics the CreateOrUpdate, by retrieving the object to understand if it must be created or updated, -// along adding the additional metadata, if required. -func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string, annotations map[string]string, namespaces corev1.NamespaceList) ([]string, error) { - log := ctrllog.FromContext(ctx) - - errGroup := new(multierror.Group) - - var items []string - - for _, item := range namespaces.Items { - ns := item.GetName() - - errGroup.Go(func() (err error) { - actual, desired := obj.DeepCopy(), obj.DeepCopy() - // Using a deferred function to properly log the results, and adding the item to the processed set. - defer func() { - keysAndValues := []interface{}{"resource", fmt.Sprintf("%s/%s", ns, desired.GetName())} - - if err != nil { - log.Error(err, "unable to replicate resource", keysAndValues...) - - return - } - - log.Info("resource has been replicated", keysAndValues...) - - replicatedItem := &capsulev1beta2.ObjectReferenceStatus{ - Name: obj.GetName(), - } - replicatedItem.Kind = obj.GetKind() - replicatedItem.Namespace = ns - replicatedItem.APIVersion = obj.GetAPIVersion() - - items = append(items, replicatedItem.String()) - }() - - actual.SetNamespace(ns) - - _, err = controllerutil.CreateOrUpdate(ctx, r.unstructuredClient, actual, func() error { - UID := actual.GetUID() - - actual.SetUnstructuredContent(desired.Object) - actual.SetNamespace(ns) - actual.SetLabels(labels) - actual.SetAnnotations(annotations) - actual.SetResourceVersion("") - actual.SetUID(UID) - - return nil - }) - - return - }) - } - // Wait returns *multierror.Error that implements stdlib error: - // the nil check must be performed down here rather than at the caller level to avoid wrong casting. - if err := errGroup.Wait(); err != nil { - return items, err - } - - return items, nil -} diff --git a/go.mod b/go.mod index 16b7865c..daf6c332 100644 --- a/go.mod +++ b/go.mod @@ -3,68 +3,81 @@ module github.com/clastix/capsule go 1.18 require ( - github.com/go-logr/logr v0.4.0 + github.com/go-logr/logr v1.2.0 github.com/hashicorp/go-multierror v1.1.0 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.14.0 + github.com/onsi/gomega v1.18.1 github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.7.0 - go.uber.org/zap v1.18.1 + github.com/stretchr/testify v1.7.1 + go.uber.org/zap v1.19.1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - k8s.io/api v0.22.0 - k8s.io/apiextensions-apiserver v0.22.0 - k8s.io/apimachinery v0.22.0 - k8s.io/client-go v0.22.0 - k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 - sigs.k8s.io/controller-runtime v0.9.5 + k8s.io/api v0.24.2 + k8s.io/apiextensions-apiserver v0.24.2 + k8s.io/apimachinery v0.24.2 + k8s.io/client-go v0.24.2 + k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 + sigs.k8s.io/cluster-api v1.2.4 + sigs.k8s.io/controller-runtime v0.12.3 ) require ( cloud.google.com/go v0.81.0 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/evanphx/json-patch v4.11.0+incompatible // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/go-logr/zapr v0.4.0 // indirect + github.com/emicklei/go-restful v2.15.0+incompatible // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/go-logr/zapr v1.2.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/gobuffalo/flect v0.2.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.5 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.1.2 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.2.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/json-iterator/go v1.1.11 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.26.0 // indirect - github.com/prometheus/procfs v0.6.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect - golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect + golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect + golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.26.0 // indirect + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/component-base v0.22.0 // indirect - k8s.io/klog/v2 v2.9.0 // indirect - k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect + k8s.io/component-base v0.24.2 // indirect + k8s.io/klog/v2 v2.60.1 // indirect + k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect + sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 1972247b..6888b749 100644 --- a/go.sum +++ b/go.sum @@ -38,25 +38,26 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -64,9 +65,13 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e h1:GCzyKMDDjSGnlpl3clrdAK7I1AaVoaiKDOYkUzChZzg= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -77,13 +82,16 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -91,22 +99,21 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0= +github.com/coredns/corefile-migration v1.0.17 h1:tNwh8+4WOANV6NjSljwgW7qViJfhvPUt1kosj4rR8yg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -114,30 +121,35 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= +github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -151,23 +163,23 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= -github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= +github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobuffalo/flect v0.2.5 h1:H6vvsv2an0lalEaCDRThvtBfmg44W/QHXBCYUXf/6S4= +github.com/gobuffalo/flect v0.2.5/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -175,7 +187,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -211,6 +223,11 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.10.1 h1:MQBGSZGnDwh7T/un+mzGKOMz3x+4E/GDPprWjDL+1Jg= +github.com/google/cel-go v0.10.1/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -221,11 +238,13 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -240,27 +259,21 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -280,13 +293,13 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -296,13 +309,14 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -314,28 +328,27 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -343,16 +356,20 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -363,21 +380,22 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI= -github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -386,6 +404,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -394,8 +413,9 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -405,22 +425,25 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -434,48 +457,47 @@ github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= @@ -498,29 +520,35 @@ go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -544,7 +572,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -554,10 +581,10 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -576,7 +603,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -599,13 +625,17 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -617,8 +647,10 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb h1:8tDJ3aechhddbdPAxpycgXHJRMLpk/Ab+aa4OgdN5/g= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -645,10 +677,8 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -673,7 +703,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -690,17 +719,21 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -711,13 +744,12 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -731,7 +763,6 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -775,12 +806,12 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= @@ -846,8 +877,8 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -857,6 +888,9 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -878,6 +912,7 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -889,15 +924,16 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -920,8 +956,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -931,49 +968,45 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= -k8s.io/api v0.22.0 h1:elCpMZ9UE8dLdYxr55E06TmSeji9I3KH494qH70/y+c= -k8s.io/api v0.22.0/go.mod h1:0AoXXqst47OI/L0oGKq9DG61dvGRPXs7X4/B7KyjBCU= -k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE= -k8s.io/apiextensions-apiserver v0.22.0 h1:QTuZIQggaE7N8FTjur+1zxLmEPziphK7nNm8t+VNO3g= -k8s.io/apiextensions-apiserver v0.22.0/go.mod h1:+9w/QQC/lwH2qTbpqndXXjwBgidlSmytvIUww16UACE= -k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= -k8s.io/apimachinery v0.22.0 h1:CqH/BdNAzZl+sr3tc0D3VsK3u6ARVSo3GWyLmfIjbP0= -k8s.io/apimachinery v0.22.0/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU= -k8s.io/apiserver v0.22.0/go.mod h1:04kaIEzIQrTGJ5syLppQWvpkLJXQtJECHmae+ZGc/nc= -k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= -k8s.io/client-go v0.22.0 h1:sD6o9O6tCwUKCENw8v+HFsuAbq2jCu8cWC61/ydwA50= -k8s.io/client-go v0.22.0/go.mod h1:GUjIuXR5PiEv/RVK5OODUsm6eZk7wtSWZSaSJbpFdGg= -k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= -k8s.io/code-generator v0.22.0/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= -k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ= -k8s.io/component-base v0.22.0 h1:ZTmX8hUqH9T9gc0mM42O+KDgtwTYbVTt2MwmLP0eK8A= -k8s.io/component-base v0.22.0/go.mod h1:SXj6Z+V6P6GsBhHZVbWCw9hFjUdUYnJerlhhPnYCBCg= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +inet.af/netaddr v0.0.0-20220617031823-097006376321 h1:B4dC8ySKTQXasnjDTMsoCMf1sQG4WsMej0WXaHxunmU= +k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI= +k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= +k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k= +k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ= +k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM= +k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apiserver v0.24.2 h1:orxipm5elPJSkkFNlwH9ClqaKEDJJA3yR2cAAlCnyj4= +k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI= +k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA= +k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= +k8s.io/cluster-bootstrap v0.24.0 h1:MTs2x3Vfcl/PWvB5bfX7gzTFRyi4ZSbNSQgGJTCb6Sw= +k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= +k8s.io/component-base v0.24.2 h1:kwpQdoSfbcH+8MPN4tALtajLDfSfYxBDYlXobNWI6OU= +k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 h1:DnzUXII7sVg1FJ/4JX6YDRJfLNAC7idRatPwe07suiI= -k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= +k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.9.5 h1:WThcFE6cqctTn2jCZprLICO6BaKZfhsT37uAapTNfxc= -sigs.k8s.io/controller-runtime v0.9.5/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= +sigs.k8s.io/cluster-api v1.2.4 h1:wxfm/p8y+Q3qWVkkIPAIVqabA5lJVvqoRA02Nhup3uk= +sigs.k8s.io/cluster-api v1.2.4/go.mod h1:YaLJOC9mSsIOpdbh7BpthGmC8uxIJADzrMMIGpgahfM= +sigs.k8s.io/controller-runtime v0.12.3 h1:FCM8xeY/FI8hoAfh/V4XbbYMY20gElh9yh+A98usMio= +sigs.k8s.io/controller-runtime v0.12.3/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/main.go b/main.go index ccf7029a..fd713ac3 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,9 @@ import ( utilVersion "k8s.io/apimachinery/pkg/util/version" clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -71,6 +73,27 @@ func printVersion() { setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH)) } +func newDelegatingClient(cache cache.Cache, config *rest.Config, options client.Options, uncachedObjects ...client.Object) (client.Client, error) { + cl, err := client.New(config, options) + if err != nil { + return nil, err + } + + delegatingClient, err := client.NewDelegatingClient( + client.NewDelegatingClientInput{ + Client: cl, + CacheReader: cache, + UncachedObjects: uncachedObjects, + CacheUnstructured: true, + }, + ) + if err != nil { + return nil, err + } + + return delegatingClient, nil +} + // nolint:maintidx func main() { var enableLeaderElection, version bool @@ -121,6 +144,7 @@ func main() { LeaderElection: enableLeaderElection, LeaderElectionID: "42c733ea.clastix.capsule.io", HealthProbeBindAddress: ":10080", + NewClient: newDelegatingClient, }) if err != nil { setupLog.Error(err, "unable to start manager") From 28083448471e40ba67edfeb5c6bd45e0292f1d5c Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 28 Oct 2022 18:29:30 -0400 Subject: [PATCH 029/153] refactor: deprecating tenant v1alpha1 version --- api/v1alpha1/tenant_types.go | 1 + config/crd/bases/capsule.clastix.io_tenants.yaml | 3 +++ config/install.yaml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/api/v1alpha1/tenant_types.go b/api/v1alpha1/tenant_types.go index f8509f3f..e1a1e2a5 100644 --- a/api/v1alpha1/tenant_types.go +++ b/api/v1alpha1/tenant_types.go @@ -46,6 +46,7 @@ type TenantStatus struct { // +kubebuilder:printcolumn:name="Owner kind",type="string",JSONPath=".spec.owner.kind",description="The assigned Tenant owner kind" // +kubebuilder:printcolumn:name="Node selector",type="string",JSONPath=".spec.nodeSelector",description="Node Selector applied to Pods" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age" +// +kubebuilder:deprecatedversion:warning="This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version." // Tenant is the Schema for the tenants API. type Tenant struct { diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index 474977cf..9db86c24 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -42,6 +42,9 @@ spec: jsonPath: .metadata.creationTimestamp name: Age type: date + deprecated: true + deprecationWarning: This version is going to be dropped in the upcoming version + of Capsule; please, migrate to v1beta2 version. name: v1alpha1 schema: openAPIV3Schema: diff --git a/config/install.yaml b/config/install.yaml index 632a7601..d358bfa6 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -615,6 +615,8 @@ spec: jsonPath: .metadata.creationTimestamp name: Age type: date + deprecated: true + deprecationWarning: This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version. name: v1alpha1 schema: openAPIV3Schema: From 6a380b00ad2ead2c2530a52634a80698f0c5d98c Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 28 Oct 2022 18:42:21 -0400 Subject: [PATCH 030/153] style: kubebuilder annotations start with empty space --- api/v1alpha1/tenant_types.go | 2 +- api/v1beta1/namespace_options.go | 2 +- api/v1beta1/service_allowed_types.go | 6 +++--- api/v1beta1/tenant_status.go | 2 +- api/v1beta1/tenant_types.go | 8 ++++---- api/v1beta2/namespace_options.go | 2 +- api/v1beta2/tenant_status.go | 2 +- api/v1beta2/tenant_types.go | 6 +++--- api/v1beta2/tenantresource_global.go | 6 +++--- api/v1beta2/tenantresource_namespaced.go | 6 +++--- pkg/api/service_allowed_types.go | 6 +++--- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/api/v1alpha1/tenant_types.go b/api/v1alpha1/tenant_types.go index e1a1e2a5..e0b8c884 100644 --- a/api/v1alpha1/tenant_types.go +++ b/api/v1alpha1/tenant_types.go @@ -15,7 +15,7 @@ import ( type TenantSpec struct { Owner OwnerSpec `json:"owner"` - //+kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Minimum=1 NamespaceQuota *int32 `json:"namespaceQuota,omitempty"` NamespacesMetadata *AdditionalMetadata `json:"namespacesMetadata,omitempty"` ServicesMetadata *AdditionalMetadata `json:"servicesMetadata,omitempty"` diff --git a/api/v1beta1/namespace_options.go b/api/v1beta1/namespace_options.go index 88b45a28..0d7a564a 100644 --- a/api/v1beta1/namespace_options.go +++ b/api/v1beta1/namespace_options.go @@ -10,7 +10,7 @@ import ( ) type NamespaceOptions struct { - //+kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Minimum=1 // Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. Quota *int32 `json:"quota,omitempty"` // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. diff --git a/api/v1beta1/service_allowed_types.go b/api/v1beta1/service_allowed_types.go index 38e692b5..3f57c648 100644 --- a/api/v1beta1/service_allowed_types.go +++ b/api/v1beta1/service_allowed_types.go @@ -4,13 +4,13 @@ package v1beta1 type AllowedServices struct { - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. NodePort *bool `json:"nodePort,omitempty"` - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. ExternalName *bool `json:"externalName,omitempty"` - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. LoadBalancer *bool `json:"loadBalancer,omitempty"` } diff --git a/api/v1beta1/tenant_status.go b/api/v1beta1/tenant_status.go index ef248080..8122633d 100644 --- a/api/v1beta1/tenant_status.go +++ b/api/v1beta1/tenant_status.go @@ -13,7 +13,7 @@ const ( // Returns the observed state of the Tenant. type TenantStatus struct { - //+kubebuilder:default=Active + // +kubebuilder:default=Active // The operational state of the Tenant. Possible values are "Active", "Cordoned". State tenantState `json:"state"` // How many namespaces are assigned to the Tenant. diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 009a6f1a..72fa6c89 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -39,9 +39,9 @@ type TenantSpec struct { PriorityClasses *api.AllowedListSpec `json:"priorityClasses,omitempty"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status -//+kubebuilder:storageversion +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:storageversion // +kubebuilder:resource:scope=Cluster,shortName=tnt // +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant" // +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceOptions.quota",description="The max amount of Namespaces can be created" @@ -60,7 +60,7 @@ type Tenant struct { func (in *Tenant) Hub() {} -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // TenantList contains a list of Tenant. type TenantList struct { diff --git a/api/v1beta2/namespace_options.go b/api/v1beta2/namespace_options.go index a76b79a5..5be847c9 100644 --- a/api/v1beta2/namespace_options.go +++ b/api/v1beta2/namespace_options.go @@ -8,7 +8,7 @@ import ( ) type NamespaceOptions struct { - //+kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Minimum=1 // Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. Quota *int32 `json:"quota,omitempty"` // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. diff --git a/api/v1beta2/tenant_status.go b/api/v1beta2/tenant_status.go index de6b44dc..e3204ed1 100644 --- a/api/v1beta2/tenant_status.go +++ b/api/v1beta2/tenant_status.go @@ -13,7 +13,7 @@ const ( // Returns the observed state of the Tenant. type TenantStatus struct { - //+kubebuilder:default=Active + // +kubebuilder:default=Active // The operational state of the Tenant. Possible values are "Active", "Cordoned". State tenantState `json:"state"` // How many namespaces are assigned to the Tenant. diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index 04f8fa2c..f6960412 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -44,8 +44,8 @@ type TenantSpec struct { PreventDeletion bool `json:"preventDeletion,omitempty"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster,shortName=tnt // +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant" // +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceOptions.quota",description="The max amount of Namespaces can be created" @@ -72,7 +72,7 @@ func (in *Tenant) GetNamespaces() (res []string) { return } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // TenantList contains a list of Tenant. type TenantList struct { diff --git a/api/v1beta2/tenantresource_global.go b/api/v1beta2/tenantresource_global.go index fd918fa6..1feced99 100644 --- a/api/v1beta2/tenantresource_global.go +++ b/api/v1beta2/tenantresource_global.go @@ -34,8 +34,8 @@ func (p *ProcessedItems) AsSet() sets.String { return set } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster // GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. @@ -47,7 +47,7 @@ type GlobalTenantResource struct { Status GlobalTenantResourceStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // GlobalTenantResourceList contains a list of GlobalTenantResource. type GlobalTenantResourceList struct { diff --git a/api/v1beta2/tenantresource_namespaced.go b/api/v1beta2/tenantresource_namespaced.go index 3d19a952..ac6c764c 100644 --- a/api/v1beta2/tenantresource_namespaced.go +++ b/api/v1beta2/tenantresource_namespaced.go @@ -49,8 +49,8 @@ type TenantResourceStatus struct { ProcessedItems ProcessedItems `json:"processedItems"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // TenantResource allows a Tenant Owner, if enabled with proper RBAC, to propagate resources in its Namespace. // The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. @@ -63,7 +63,7 @@ type TenantResource struct { Status TenantResourceStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // TenantResourceList contains a list of TenantResource. type TenantResourceList struct { diff --git a/pkg/api/service_allowed_types.go b/pkg/api/service_allowed_types.go index 3f8369a4..e1e604e2 100644 --- a/pkg/api/service_allowed_types.go +++ b/pkg/api/service_allowed_types.go @@ -6,13 +6,13 @@ package api // +kubebuilder:object:generate=true type AllowedServices struct { - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. NodePort *bool `json:"nodePort,omitempty"` - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. ExternalName *bool `json:"externalName,omitempty"` - //+kubebuilder:default=true + // +kubebuilder:default=true // Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. LoadBalancer *bool `json:"loadBalancer,omitempty"` } From d58c5124b2ff5921462728fb49c51970252401ea Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:57:54 +0200 Subject: [PATCH 031/153] chore(helm)!: release of v0.2.0 --- charts/capsule/Chart.yaml | 2 +- charts/capsule/crds/tenant-crd.yaml | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 56757dd6..3516c786 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,7 +21,7 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.1.12 +version: 0.2.0 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index 5b36f41e..d018eabf 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -54,6 +54,8 @@ spec: jsonPath: .metadata.creationTimestamp name: Age type: date + deprecated: true + deprecationWarning: This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version. name: v1alpha1 schema: openAPIV3Schema: @@ -211,11 +213,11 @@ spec: type: integer namespacesMetadata: properties: - annotations: + additionalAnnotations: additionalProperties: type: string type: object - labels: + additionalLabels: additionalProperties: type: string type: object @@ -554,11 +556,11 @@ spec: type: array servicesMetadata: properties: - annotations: + additionalAnnotations: additionalProperties: type: string type: object - labels: + additionalLabels: additionalProperties: type: string type: object From ea01b9d1f9e568e418345eb7f74d07f22ca7eb62 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 19 Nov 2022 13:01:51 +0100 Subject: [PATCH 032/153] test: support for endpointslice/v1 for k8s v1.25 --- e2e/service_metadata_test.go | 47 ++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index 8ed2557a..3f86772b 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -220,7 +220,7 @@ var _ = Describe("adding metadata to Service objects", func() { }) }) - It("should apply them to EndpointSlice", func() { + It("should apply them to EndpointSlice in v1", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { missingAPIError := &meta.NoKindMatchError{} if errors.As(err, &missingAPIError) { @@ -231,10 +231,34 @@ var _ = Describe("adding metadata to Service objects", func() { ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + // Waiting for the reconciliation of required RBAC + EventuallyCreation(func() (err error) { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "container", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + }, + } + _, err = ownerClient(tnt.Spec.Owners[0]).CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + + return + }).Should(Succeed()) var eps client.Object - if version := GetKubernetesVersion(); version.Major() == 1 && version.Minor() < 25 { + if err := k8sClient.List(context.Background(), &discoveryv1.EndpointSliceList{}); err != nil { + missingAPIError := &meta.NoKindMatchError{} + if errors.As(err, &missingAPIError) { + Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) + } + eps = &discoveryv1beta1.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ Name: "endpointslice-metadata", @@ -273,25 +297,6 @@ var _ = Describe("adding metadata to Service objects", func() { }, } } - // Waiting for the reconciliation of required RBAC - EventuallyCreation(func() (err error) { - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "container", - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "container", - Image: "quay.io/google-containers/pause-amd64:3.0", - }, - }, - }, - } - _, err = ownerClient(tnt.Spec.Owners[0]).CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) - - return - }).Should(Succeed()) EventuallyCreation(func() (err error) { return k8sClient.Create(context.TODO(), eps) From 9d6f766cc1af157bb548284ea8dbd37e9ae80fa4 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 19 Nov 2022 13:04:29 +0100 Subject: [PATCH 033/153] fix(makefile): avoid race condition for local kind cluster --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c496a090..b2d08df0 100644 --- a/Makefile +++ b/Makefile @@ -226,7 +226,7 @@ e2e/%: ginkgo $(MAKE) e2e-build/$* && $(MAKE) e2e-exec || $(MAKE) e2e-destroy e2e-build/%: - kind create cluster --name capsule --image=kindest/node:$* + kind create cluster --wait=60s --name capsule --image=kindest/node:$* make docker-build kind load docker-image --nodes capsule-control-plane --name capsule $(IMG) helm upgrade \ @@ -247,4 +247,3 @@ e2e-exec: e2e-destroy: kind delete cluster --name capsule - From e14e12c2877c6d94532aa83e91d3cc9d659a6266 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 27 Sep 2022 18:27:47 +0200 Subject: [PATCH 034/153] refactor: abstracting types used by several api versions --- charts/capsule/crds/tenant-crd.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index d018eabf..7dd0cd98 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -213,11 +213,11 @@ spec: type: integer namespacesMetadata: properties: - additionalAnnotations: + annotations: additionalProperties: type: string type: object - additionalLabels: + labels: additionalProperties: type: string type: object @@ -556,11 +556,11 @@ spec: type: array servicesMetadata: properties: - additionalAnnotations: + annotations: additionalProperties: type: string type: object - additionalLabels: + labels: additionalProperties: type: string type: object From 34a948da817638672650a58d26b985d176cde1bc Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 13 Oct 2022 15:57:54 +0200 Subject: [PATCH 035/153] chore(helm)!: release of v0.2.0 --- charts/capsule/crds/tenant-crd.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index 7dd0cd98..d018eabf 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -213,11 +213,11 @@ spec: type: integer namespacesMetadata: properties: - annotations: + additionalAnnotations: additionalProperties: type: string type: object - labels: + additionalLabels: additionalProperties: type: string type: object @@ -556,11 +556,11 @@ spec: type: array servicesMetadata: properties: - annotations: + additionalAnnotations: additionalProperties: type: string type: object - labels: + additionalLabels: additionalProperties: type: string type: object From 0f580ef37994672b3e5d8f990b72fb44ab710b5b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 22 Dec 2022 18:56:35 +0100 Subject: [PATCH 036/153] feat(api): promoting v1beta2 as storage version --- api/v1alpha1/capsuleconfiguration_types.go | 1 - api/v1beta1/tenant_types.go | 1 - api/v1beta2/capsuleconfiguration_types.go | 1 + api/v1beta2/tenant_types.go | 1 + 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/v1alpha1/capsuleconfiguration_types.go b/api/v1alpha1/capsuleconfiguration_types.go index 147bc485..ac404a25 100644 --- a/api/v1alpha1/capsuleconfiguration_types.go +++ b/api/v1alpha1/capsuleconfiguration_types.go @@ -20,7 +20,6 @@ type CapsuleConfigurationSpec struct { ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"` } -// +kubebuilder:storageversion // +kubebuilder:object:root=true // +kubebuilder:resource:scope=Cluster diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 72fa6c89..29cfd5b4 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -41,7 +41,6 @@ type TenantSpec struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// +kubebuilder:storageversion // +kubebuilder:resource:scope=Cluster,shortName=tnt // +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant" // +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceOptions.quota",description="The max amount of Namespaces can be created" diff --git a/api/v1beta2/capsuleconfiguration_types.go b/api/v1beta2/capsuleconfiguration_types.go index d9a56424..a45f2ea4 100644 --- a/api/v1beta2/capsuleconfiguration_types.go +++ b/api/v1beta2/capsuleconfiguration_types.go @@ -54,6 +54,7 @@ type CapsuleResources struct { // +kubebuilder:object:root=true // +kubebuilder:resource:scope=Cluster +// +kubebuilder:storageversion // CapsuleConfiguration is the Schema for the Capsule configuration API. type CapsuleConfiguration struct { diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index f6960412..47f3237b 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -45,6 +45,7 @@ type TenantSpec struct { } // +kubebuilder:object:root=true +// +kubebuilder:storageversion // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster,shortName=tnt // +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The actual state of the Tenant" From 0b057d63b607bffb627cd84e9251b50746319349 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 22 Dec 2022 18:56:57 +0100 Subject: [PATCH 037/153] chore(kustomize): promoting v1beta2 as storage version --- .../bases/capsule.clastix.io_capsuleconfigurations.yaml | 4 ++-- config/crd/bases/capsule.clastix.io_tenants.yaml | 4 ++-- config/install.yaml | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml index aae170bf..460208a9 100644 --- a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml +++ b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml @@ -57,7 +57,7 @@ spec: type: object type: object served: true - storage: true + storage: false - name: v1beta2 schema: openAPIV3Schema: @@ -169,4 +169,4 @@ spec: type: object type: object served: true - storage: false + storage: true diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index 9db86c24..7562231e 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -1917,7 +1917,7 @@ spec: type: object type: object served: true - storage: true + storage: false subresources: status: {} - additionalPrinterColumns: @@ -3012,6 +3012,6 @@ spec: type: object type: object served: true - storage: false + storage: true subresources: status: {} diff --git a/config/install.yaml b/config/install.yaml index d358bfa6..0c04ecc9 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -65,7 +65,7 @@ spec: type: object type: object served: true - storage: true + storage: false - name: v1beta2 schema: openAPIV3Schema: @@ -154,7 +154,7 @@ spec: type: object type: object served: true - storage: false + storage: true --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -1835,7 +1835,7 @@ spec: type: object type: object served: true - storage: true + storage: false subresources: status: {} - additionalPrinterColumns: @@ -2560,7 +2560,7 @@ spec: type: object type: object served: true - storage: false + storage: true subresources: status: {} --- From 2cb37abc51f10a0d08a1569ade5885fcbcd49a3b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 22 Dec 2022 18:57:22 +0100 Subject: [PATCH 038/153] chore(helm): promoting v1beta2 as storage version --- charts/capsule/crds/capsuleconfiguration-crd.yaml | 4 ++-- charts/capsule/crds/tenant-crd.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/capsule/crds/capsuleconfiguration-crd.yaml b/charts/capsule/crds/capsuleconfiguration-crd.yaml index d20fb126..a2a42efa 100644 --- a/charts/capsule/crds/capsuleconfiguration-crd.yaml +++ b/charts/capsule/crds/capsuleconfiguration-crd.yaml @@ -60,7 +60,7 @@ spec: type: object type: object served: true - storage: true + storage: false - name: v1beta2 schema: openAPIV3Schema: @@ -149,4 +149,4 @@ spec: type: object type: object served: true - storage: false + storage: true diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index d018eabf..90ec09ec 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -1274,7 +1274,7 @@ spec: type: object type: object served: true - storage: true + storage: false subresources: status: {} - additionalPrinterColumns: @@ -1999,6 +1999,6 @@ spec: type: object type: object served: true - storage: false + storage: true subresources: status: {} From 43bd2491aea348ecb5a46b20a8aabe35a3200545 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 10:23:29 +0100 Subject: [PATCH 039/153] refactor(api): switching to v1beta2 as storage version --- api/v1alpha1/capsuleconfiguration_webhook.go | 21 +++ api/v1alpha1/tenant_func.go | 8 -- api/v1beta1/namespace_options.go | 16 +-- api/v1beta1/tenant_webhook.go | 21 +++ api/v1beta2/capsuleconfiguration_types.go | 5 +- api/v1beta2/tenant_annotations.go | 17 +++ api/v1beta2/tenant_conversion_hub.go | 31 ++--- controllers/servicelabels/abstract.go | 10 +- controllers/tenant/limitranges.go | 8 +- controllers/tenant/manager.go | 14 +- controllers/tenant/namespaces.go | 31 ++--- controllers/tenant/networkpolicies.go | 8 +- controllers/tenant/resourcequotas.go | 14 +- controllers/tenant/resourcequotas_quota.go | 10 +- controllers/tenant/rolebindings.go | 18 +-- controllers/tenant/utils.go | 4 +- main.go | 12 +- .../api/annotations.go | 15 +-- pkg/configuration/client.go | 126 ++++-------------- pkg/configuration/configuration.go | 11 +- pkg/indexer/indexer.go | 2 - pkg/indexer/namespace/namespaces.go | 4 +- pkg/indexer/tenant/owner.go | 8 +- pkg/utils/node_selector.go | 4 +- pkg/utils/owner.go | 4 +- pkg/webhook/ingress/utils.go | 6 +- pkg/webhook/ingress/validate_class.go | 8 +- pkg/webhook/ingress/validate_collision.go | 10 +- pkg/webhook/ingress/validate_hostnames.go | 8 +- pkg/webhook/ingress/validate_wildcard.go | 7 +- pkg/webhook/namespace/errors.go | 12 +- pkg/webhook/namespace/freezed.go | 14 +- pkg/webhook/namespace/patch.go | 8 +- pkg/webhook/namespace/prefix.go | 4 +- pkg/webhook/namespace/quota.go | 4 +- pkg/webhook/namespace/user_metadata.go | 38 +++--- pkg/webhook/networkpolicy/validating.go | 10 +- pkg/webhook/node/errors.go | 12 +- pkg/webhook/ownerreference/patching.go | 22 +-- pkg/webhook/pod/containerregistry.go | 4 +- pkg/webhook/pod/imagepullpolicy.go | 4 +- pkg/webhook/pod/imagepullpolicy_pullpolicy.go | 4 +- pkg/webhook/pod/priorityclass.go | 4 +- pkg/webhook/pvc/validating.go | 4 +- pkg/webhook/route/tenants.go | 2 +- pkg/webhook/service/validating.go | 4 +- pkg/webhook/tenant/containerregistry_regex.go | 4 +- pkg/webhook/tenant/cordoning.go | 6 +- pkg/webhook/tenant/custom_resource_quota.go | 22 +-- .../tenant/forbidden_annotations_regex.go | 19 +-- pkg/webhook/tenant/freezed_emitter.go | 10 +- pkg/webhook/tenant/hostname_regex.go | 4 +- pkg/webhook/tenant/ingressclass_regex.go | 4 +- pkg/webhook/tenant/name.go | 4 +- pkg/webhook/tenant/protected.go | 9 +- pkg/webhook/tenant/rolebindings_regex.go | 4 +- pkg/webhook/tenant/serviceaccount_format.go | 4 +- pkg/webhook/tenant/storageclass_regex.go | 4 +- pkg/webhook/utils/is_capsule_user.go | 4 +- pkg/webhook/utils/is_tenant_owner.go | 8 +- 60 files changed, 344 insertions(+), 373 deletions(-) create mode 100644 api/v1alpha1/capsuleconfiguration_webhook.go create mode 100644 api/v1beta1/tenant_webhook.go create mode 100644 api/v1beta2/tenant_annotations.go rename api/v1beta1/tenant_annotations.go => pkg/api/annotations.go (63%) diff --git a/api/v1alpha1/capsuleconfiguration_webhook.go b/api/v1alpha1/capsuleconfiguration_webhook.go new file mode 100644 index 00000000..45a4221d --- /dev/null +++ b/api/v1alpha1/capsuleconfiguration_webhook.go @@ -0,0 +1,21 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1alpha1 + +import ( + "os" + + ctrl "sigs.k8s.io/controller-runtime" +) + +func (in *CapsuleConfiguration) SetupWebhookWithManager(mgr ctrl.Manager) error { + certData, _ := os.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt") + if len(certData) == 0 { + return nil + } + + return ctrl.NewWebhookManagedBy(mgr). + For(in). + Complete() +} diff --git a/api/v1alpha1/tenant_func.go b/api/v1alpha1/tenant_func.go index 6342f2d1..b00147b7 100644 --- a/api/v1alpha1/tenant_func.go +++ b/api/v1alpha1/tenant_func.go @@ -9,14 +9,6 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (in *Tenant) IsCordoned() bool { - if v, ok := in.Labels["capsule.clastix.io/cordon"]; ok && v == "enabled" { - return true - } - - return false -} - func (in *Tenant) IsFull() bool { // we don't have limits on assigned Namespaces if in.Spec.NamespaceQuota == nil { diff --git a/api/v1beta1/namespace_options.go b/api/v1beta1/namespace_options.go index 0d7a564a..c4a2aed5 100644 --- a/api/v1beta1/namespace_options.go +++ b/api/v1beta1/namespace_options.go @@ -18,11 +18,11 @@ type NamespaceOptions struct { } func (in *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool { - if _, ok := in.Annotations[ForbiddenNamespaceLabelsAnnotation]; ok { + if _, ok := in.Annotations[api.ForbiddenNamespaceLabelsAnnotation]; ok { return true } - if _, ok := in.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation]; ok { + if _, ok := in.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; ok { return true } @@ -30,11 +30,11 @@ func (in *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool { } func (in *Tenant) hasForbiddenNamespaceAnnotationsAnnotations() bool { - if _, ok := in.Annotations[ForbiddenNamespaceAnnotationsAnnotation]; ok { + if _, ok := in.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; ok { return true } - if _, ok := in.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok { + if _, ok := in.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok { return true } @@ -47,8 +47,8 @@ func (in *Tenant) ForbiddenUserNamespaceLabels() *api.ForbiddenListSpec { } return &api.ForbiddenListSpec{ - Exact: strings.Split(in.Annotations[ForbiddenNamespaceLabelsAnnotation], ","), - Regex: in.Annotations[ForbiddenNamespaceLabelsRegexpAnnotation], + Exact: strings.Split(in.Annotations[api.ForbiddenNamespaceLabelsAnnotation], ","), + Regex: in.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation], } } @@ -58,7 +58,7 @@ func (in *Tenant) ForbiddenUserNamespaceAnnotations() *api.ForbiddenListSpec { } return &api.ForbiddenListSpec{ - Exact: strings.Split(in.Annotations[ForbiddenNamespaceAnnotationsAnnotation], ","), - Regex: in.Annotations[ForbiddenNamespaceAnnotationsRegexpAnnotation], + Exact: strings.Split(in.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation], ","), + Regex: in.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation], } } diff --git a/api/v1beta1/tenant_webhook.go b/api/v1beta1/tenant_webhook.go new file mode 100644 index 00000000..58ffec85 --- /dev/null +++ b/api/v1beta1/tenant_webhook.go @@ -0,0 +1,21 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta1 + +import ( + "os" + + ctrl "sigs.k8s.io/controller-runtime" +) + +func (in *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error { + certData, _ := os.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt") + if len(certData) == 0 { + return nil + } + + return ctrl.NewWebhookManagedBy(mgr). + For(in). + Complete() +} diff --git a/api/v1beta2/capsuleconfiguration_types.go b/api/v1beta2/capsuleconfiguration_types.go index a45f2ea4..a5854b51 100644 --- a/api/v1beta2/capsuleconfiguration_types.go +++ b/api/v1beta2/capsuleconfiguration_types.go @@ -22,10 +22,11 @@ type CapsuleConfigurationSpec struct { ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"` // Allows to set different name rather than the canonical one for the Capsule configuration objects, // such as webhook secret or configurations. - CapsuleResources CapsuleResources `json:"overrides"` + // +kubebuilder:default={TLSSecretName:"capsule-tls",mutatingWebhookConfigurationName:"capsule-mutating-webhook-configuration",validatingWebhookConfigurationName:"capsule-validating-webhook-configuration"} + CapsuleResources CapsuleResources `json:"overrides,omitempty"` // Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. // This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes. - NodeMetadata *NodeMetadata `json:"nodeMetadata"` + NodeMetadata *NodeMetadata `json:"nodeMetadata,omitempty"` // Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks // when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager. // +kubebuilder:default=true diff --git a/api/v1beta2/tenant_annotations.go b/api/v1beta2/tenant_annotations.go new file mode 100644 index 00000000..4f02bdf6 --- /dev/null +++ b/api/v1beta2/tenant_annotations.go @@ -0,0 +1,17 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package v1beta2 + +import ( + "fmt" + "strings" +) + +func UsedQuotaFor(resource fmt.Stringer) string { + return "quota.capsule.clastix.io/used-" + strings.ReplaceAll(resource.String(), "/", "_") +} + +func HardQuotaFor(resource fmt.Stringer) string { + return "quota.capsule.clastix.io/hard-" + strings.ReplaceAll(resource.String(), "/", "_") +} diff --git a/api/v1beta2/tenant_conversion_hub.go b/api/v1beta2/tenant_conversion_hub.go index de3dfb3a..4256e252 100644 --- a/api/v1beta2/tenant_conversion_hub.go +++ b/api/v1beta2/tenant_conversion_hub.go @@ -11,6 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/conversion" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + "github.com/clastix/capsule/pkg/api" ) func (in *Tenant) ConvertFrom(raw conversion.Hub) error { @@ -58,28 +59,28 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { in.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation]; found { + if value, found := annotations[api.ForbiddenNamespaceLabelsAnnotation]; found { in.Spec.NamespaceOptions.ForbiddenLabels.Exact = strings.Split(value, ",") - delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsAnnotation) + delete(annotations, api.ForbiddenNamespaceLabelsAnnotation) } - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation]; found { + if value, found := annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; found { in.Spec.NamespaceOptions.ForbiddenLabels.Regex = value - delete(annotations, capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation) + delete(annotations, api.ForbiddenNamespaceLabelsRegexpAnnotation) } - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation]; found { + if value, found := annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; found { in.Spec.NamespaceOptions.ForbiddenAnnotations.Exact = strings.Split(value, ",") - delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation) + delete(annotations, api.ForbiddenNamespaceAnnotationsAnnotation) } - if value, found := annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found { + if value, found := annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found { in.Spec.NamespaceOptions.ForbiddenAnnotations.Regex = value - delete(annotations, capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation) + delete(annotations, api.ForbiddenNamespaceAnnotationsRegexpAnnotation) } } @@ -126,10 +127,10 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { in.Spec.Cordoned = value } - if _, found := annotations[capsulev1beta1.ProtectedTenantAnnotation]; found { + if _, found := annotations[api.ProtectedTenantAnnotation]; found { in.Spec.PreventDeletion = true - delete(annotations, capsulev1beta1.ProtectedTenantAnnotation) + delete(annotations, api.ProtectedTenantAnnotation) } in.SetAnnotations(annotations) @@ -189,19 +190,19 @@ func (in *Tenant) ConvertTo(raw conversion.Hub) error { dst.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata if exact := nsOpts.ForbiddenAnnotations.Exact; len(exact) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",") + annotations[api.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",") } if regex := nsOpts.ForbiddenAnnotations.Regex; len(regex) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex + annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex } if exact := nsOpts.ForbiddenLabels.Exact; len(exact) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",") + annotations[api.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",") } if regex := nsOpts.ForbiddenLabels.Regex; len(regex) > 0 { - annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation] = regex + annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation] = regex } } @@ -233,7 +234,7 @@ func (in *Tenant) ConvertTo(raw conversion.Hub) error { dst.Spec.PriorityClasses = in.Spec.PriorityClasses if in.Spec.PreventDeletion { - annotations[capsulev1beta1.ProtectedTenantAnnotation] = "true" //nolint:goconst + annotations[api.ProtectedTenantAnnotation] = "true" //nolint:goconst } if in.Spec.Cordoned { diff --git a/controllers/servicelabels/abstract.go b/controllers/servicelabels/abstract.go index 54dd2dfb..c0a8c189 100644 --- a/controllers/servicelabels/abstract.go +++ b/controllers/servicelabels/abstract.go @@ -20,7 +20,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/utils" ) @@ -70,15 +70,15 @@ func (r *abstractServiceLabelsReconciler) Reconcile(ctx context.Context, request return reconcile.Result{}, err } -func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespacedName types.NamespacedName, client client.Client) (*capsulev1beta1.Tenant, error) { +func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespacedName types.NamespacedName, client client.Client) (*capsulev1beta2.Tenant, error) { ns := &corev1.Namespace{} - tenant := &capsulev1beta1.Tenant{} + tenant := &capsulev1beta2.Tenant{} if err := client.Get(ctx, types.NamespacedName{Name: namespacedName.Namespace}, ns); err != nil { return nil, err } - capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) + capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{}) if _, ok := ns.GetLabels()[capsuleLabel]; !ok { return nil, NewNonTenantObject(namespacedName.Name) } @@ -117,7 +117,7 @@ func (r *abstractServiceLabelsReconciler) forOptionPerInstanceName(ctx context.C } func (r *abstractServiceLabelsReconciler) IsNamespaceInTenant(ctx context.Context, namespace string) bool { - tl := &capsulev1beta1.TenantList{} + tl := &capsulev1beta2.TenantList{} if err := r.client.List(ctx, tl, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", namespace), }); err != nil { diff --git a/controllers/tenant/limitranges.go b/controllers/tenant/limitranges.go index 59e4e152..bfa049ff 100644 --- a/controllers/tenant/limitranges.go +++ b/controllers/tenant/limitranges.go @@ -13,13 +13,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/utils" ) // nolint:dupl // Ensuring all the LimitRange are applied to each Namespace handled by the Tenant. -func (r *Manager) syncLimitRanges(ctx context.Context, tenant *capsulev1beta1.Tenant) error { +func (r *Manager) syncLimitRanges(ctx context.Context, tenant *capsulev1beta2.Tenant) error { // getting requested LimitRange keys keys := make([]string, 0, len(tenant.Spec.LimitRanges.Items)) @@ -40,11 +40,11 @@ func (r *Manager) syncLimitRanges(ctx context.Context, tenant *capsulev1beta1.Te return group.Wait() } -func (r *Manager) syncLimitRange(ctx context.Context, tenant *capsulev1beta1.Tenant, namespace string, keys []string) (err error) { +func (r *Manager) syncLimitRange(ctx context.Context, tenant *capsulev1beta2.Tenant, namespace string, keys []string) (err error) { // getting LimitRange labels for the mutateFn var tenantLabel, limitRangeLabel string - if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil { return err } diff --git a/controllers/tenant/manager.go b/controllers/tenant/manager.go index 1ea17a12..c89c6cf9 100644 --- a/controllers/tenant/manager.go +++ b/controllers/tenant/manager.go @@ -18,7 +18,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) type Manager struct { @@ -30,7 +30,7 @@ type Manager struct { func (r *Manager) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&capsulev1beta1.Tenant{}). + For(&capsulev1beta2.Tenant{}). Owns(&corev1.Namespace{}). Owns(&networkingv1.NetworkPolicy{}). Owns(&corev1.LimitRange{}). @@ -42,7 +42,7 @@ func (r *Manager) SetupWithManager(mgr ctrl.Manager) error { func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ctrl.Result, err error) { r.Log = r.Log.WithValues("Request.Name", request.Name) // Fetch the Tenant instance - instance := &capsulev1beta1.Tenant{} + instance := &capsulev1beta2.Tenant{} if err = r.Get(ctx, request.NamespacedName, instance); err != nil { if apierrors.IsNotFound(err) { r.Log.Info("Request object not found, could have been deleted after reconcile request") @@ -130,12 +130,12 @@ func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ct return ctrl.Result{}, err } -func (r *Manager) updateTenantStatus(ctx context.Context, tnt *capsulev1beta1.Tenant) error { +func (r *Manager) updateTenantStatus(ctx context.Context, tnt *capsulev1beta2.Tenant) error { return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { - if tnt.IsCordoned() { - tnt.Status.State = capsulev1beta1.TenantStateCordoned + if tnt.Spec.Cordoned { + tnt.Status.State = capsulev1beta2.TenantStateCordoned } else { - tnt.Status.State = capsulev1beta1.TenantStateActive + tnt.Status.State = capsulev1beta2.TenantStateActive } return r.Client.Status().Update(ctx, tnt) diff --git a/controllers/tenant/namespaces.go b/controllers/tenant/namespaces.go index 53a9aca9..e0ec2795 100644 --- a/controllers/tenant/namespaces.go +++ b/controllers/tenant/namespaces.go @@ -16,12 +16,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/api" "github.com/clastix/capsule/pkg/utils" ) // Ensuring all annotations are applied to each Namespace handled by the Tenant. -func (r *Manager) syncNamespaces(ctx context.Context, tenant *capsulev1beta1.Tenant) (err error) { +func (r *Manager) syncNamespaces(ctx context.Context, tenant *capsulev1beta2.Tenant) (err error) { group := new(errgroup.Group) for _, item := range tenant.Status.Namespaces { @@ -42,7 +43,7 @@ func (r *Manager) syncNamespaces(ctx context.Context, tenant *capsulev1beta1.Ten } // nolint:gocognit -func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, tnt *capsulev1beta1.Tenant) (err error) { +func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, tnt *capsulev1beta2.Tenant) (err error) { var res controllerutil.OperationResult err = retry.RetryOnConflict(retry.DefaultBackoff, func() (conflictErr error) { @@ -51,7 +52,7 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t return } - capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) + capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{}) res, conflictErr = controllerutil.CreateOrUpdate(ctx, r.Client, ns, func() error { annotations := make(map[string]string) @@ -103,20 +104,20 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t } } - if value, ok := tnt.Annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation]; ok { - annotations[capsulev1beta1.ForbiddenNamespaceLabelsAnnotation] = value + if value, ok := tnt.Annotations[api.ForbiddenNamespaceLabelsAnnotation]; ok { + annotations[api.ForbiddenNamespaceLabelsAnnotation] = value } - if value, ok := tnt.Annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation]; ok { - annotations[capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation] = value + if value, ok := tnt.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; ok { + annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation] = value } - if value, ok := tnt.Annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation]; ok { - annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation] = value + if value, ok := tnt.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; ok { + annotations[api.ForbiddenNamespaceAnnotationsAnnotation] = value } - if value, ok := tnt.Annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok { - annotations[capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation] = value + if value, ok := tnt.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok { + annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation] = value } if ns.Annotations == nil { @@ -146,11 +147,11 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t return err } -func (r *Manager) ensureNamespaceCount(ctx context.Context, tenant *capsulev1beta1.Tenant) error { +func (r *Manager) ensureNamespaceCount(ctx context.Context, tenant *capsulev1beta2.Tenant) error { return retry.RetryOnConflict(retry.DefaultBackoff, func() error { tenant.Status.Size = uint(len(tenant.Status.Namespaces)) - found := &capsulev1beta1.Tenant{} + found := &capsulev1beta2.Tenant{} if err := r.Client.Get(ctx, types.NamespacedName{Name: tenant.GetName()}, found); err != nil { return err } @@ -161,7 +162,7 @@ func (r *Manager) ensureNamespaceCount(ctx context.Context, tenant *capsulev1bet }) } -func (r *Manager) collectNamespaces(ctx context.Context, tenant *capsulev1beta1.Tenant) error { +func (r *Manager) collectNamespaces(ctx context.Context, tenant *capsulev1beta2.Tenant) error { return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) { list := &corev1.NamespaceList{} err = r.Client.List(ctx, list, client.MatchingFieldsSelector{ diff --git a/controllers/tenant/networkpolicies.go b/controllers/tenant/networkpolicies.go index 90f0379d..4d155fad 100644 --- a/controllers/tenant/networkpolicies.go +++ b/controllers/tenant/networkpolicies.go @@ -13,13 +13,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/utils" ) // nolint:dupl // Ensuring all the NetworkPolicies are applied to each Namespace handled by the Tenant. -func (r *Manager) syncNetworkPolicies(ctx context.Context, tenant *capsulev1beta1.Tenant) error { +func (r *Manager) syncNetworkPolicies(ctx context.Context, tenant *capsulev1beta2.Tenant) error { // getting requested NetworkPolicy keys keys := make([]string, 0, len(tenant.Spec.NetworkPolicies.Items)) @@ -40,14 +40,14 @@ func (r *Manager) syncNetworkPolicies(ctx context.Context, tenant *capsulev1beta return group.Wait() } -func (r *Manager) syncNetworkPolicy(ctx context.Context, tenant *capsulev1beta1.Tenant, namespace string, keys []string) (err error) { +func (r *Manager) syncNetworkPolicy(ctx context.Context, tenant *capsulev1beta2.Tenant, namespace string, keys []string) (err error) { if err = r.pruningResources(ctx, namespace, keys, &networkingv1.NetworkPolicy{}); err != nil { return err } // getting NetworkPolicy labels for the mutateFn var tenantLabel, networkPolicyLabel string - if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil { return err } diff --git a/controllers/tenant/resourcequotas.go b/controllers/tenant/resourcequotas.go index 348877fc..0f740040 100644 --- a/controllers/tenant/resourcequotas.go +++ b/controllers/tenant/resourcequotas.go @@ -19,7 +19,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" "github.com/clastix/capsule/pkg/utils" ) @@ -37,11 +37,11 @@ import ( // // In case of Namespace-scoped Resource Budget, we're just replicating the resources across all registered Namespaces. // nolint:gocognit -func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta1.Tenant) (err error) { +func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta2.Tenant) (err error) { // getting ResourceQuota labels for the mutateFn var tenantLabel, typeLabel string - if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil { return err } @@ -158,11 +158,11 @@ func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta1 return group.Wait() } -func (r *Manager) syncResourceQuota(ctx context.Context, tenant *capsulev1beta1.Tenant, namespace string, keys []string) (err error) { +func (r *Manager) syncResourceQuota(ctx context.Context, tenant *capsulev1beta2.Tenant, namespace string, keys []string) (err error) { // getting ResourceQuota labels for the mutateFn var tenantLabel, typeLabel string - if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil { return err } @@ -238,8 +238,8 @@ func (r *Manager) resourceQuotasUpdate(ctx context.Context, resourceName corev1. found.Annotations = make(map[string]string) } found.Labels = rq.Labels - found.Annotations[capsulev1beta1.UsedQuotaFor(resourceName)] = actual.String() - found.Annotations[capsulev1beta1.HardQuotaFor(resourceName)] = limit.String() + found.Annotations[capsulev1beta2.UsedQuotaFor(resourceName)] = actual.String() + found.Annotations[capsulev1beta2.HardQuotaFor(resourceName)] = limit.String() // Updating the Resource according to the actual.Cmp result found.Spec.Hard = rq.Spec.Hard diff --git a/controllers/tenant/resourcequotas_quota.go b/controllers/tenant/resourcequotas_quota.go index c20be18f..ac370a2a 100644 --- a/controllers/tenant/resourcequotas_quota.go +++ b/controllers/tenant/resourcequotas_quota.go @@ -16,10 +16,10 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/util/retry" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) -func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *capsulev1beta1.Tenant) error { +func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *capsulev1beta2.Tenant) error { type resource struct { kind string group string @@ -29,7 +29,7 @@ func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *cap var resourceList []resource for k := range tenant.GetAnnotations() { - if !strings.HasPrefix(k, capsulev1beta1.ResourceQuotaAnnotationPrefix) { + if !strings.HasPrefix(k, capsulev1beta2.ResourceQuotaAnnotationPrefix) { continue } @@ -69,7 +69,7 @@ func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *cap defer func() { for gvk, used := range usedMap { err := retry.RetryOnConflict(retry.DefaultBackoff, func() (retryErr error) { - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} if retryErr = r.Client.Get(ctx, types.NamespacedName{Name: tenant.GetName()}, tnt); retryErr != nil { return } @@ -78,7 +78,7 @@ func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *cap tnt.Annotations = make(map[string]string) } - tnt.Annotations[capsulev1beta1.UsedAnnotationForResource(gvk)] = fmt.Sprintf("%d", used) + tnt.Annotations[capsulev1beta2.UsedAnnotationForResource(gvk)] = fmt.Sprintf("%d", used) return r.Client.Update(ctx, tnt) }) diff --git a/controllers/tenant/rolebindings.go b/controllers/tenant/rolebindings.go index a1d0126d..236bfec7 100644 --- a/controllers/tenant/rolebindings.go +++ b/controllers/tenant/rolebindings.go @@ -14,14 +14,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" "github.com/clastix/capsule/pkg/utils" ) // ownerClusterRoleBindings generates a Capsule AdditionalRoleBinding object for the Owner dynamic clusterrole in order // to take advantage of the additional role binding feature. -func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clusterRole string) api.AdditionalRoleBindingsSpec { +func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta2.OwnerSpec, clusterRole string) api.AdditionalRoleBindingsSpec { var subject rbacv1.Subject if owner.Kind == "ServiceAccount" { @@ -50,7 +50,7 @@ func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta1.OwnerSpec, clust // Sync the dynamic Tenant Owner specific cluster-roles and additional Role Bindings, which can be used in many ways: // applying Pod Security Policies or giving access to CRDs or specific API groups. -func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta1.Tenant) (err error) { +func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta2.Tenant) (err error) { // hashing the RoleBinding name due to DNS RFC-1123 applied to Kubernetes labels hashFn := func(binding api.AdditionalRoleBindingsSpec) string { h := fnv.New64a() @@ -66,8 +66,8 @@ func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta1.T // getting requested Role Binding keys keys := make([]string, 0, len(tenant.Spec.Owners)) // Generating for dynamic tenant owners cluster roles - for index, owner := range tenant.Spec.Owners { - for _, clusterRoleName := range owner.GetRoles(*tenant, index) { + for _, owner := range tenant.Spec.Owners { + for _, clusterRoleName := range owner.ClusterRoles { cr := r.ownerClusterRoleBindings(owner, clusterRoleName) keys = append(keys, hashFn(cr)) @@ -91,10 +91,10 @@ func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta1.T return group.Wait() } -func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsulev1beta1.Tenant, ns string, keys []string, hashFn func(binding api.AdditionalRoleBindingsSpec) string) (err error) { +func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsulev1beta2.Tenant, ns string, keys []string, hashFn func(binding api.AdditionalRoleBindingsSpec) string) (err error) { var tenantLabel, roleBindingLabel string - if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil { + if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil { return } @@ -108,8 +108,8 @@ func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsule var roleBindings []api.AdditionalRoleBindingsSpec - for index, owner := range tenant.Spec.Owners { - for _, clusterRoleName := range owner.GetRoles(*tenant, index) { + for _, owner := range tenant.Spec.Owners { + for _, clusterRoleName := range owner.ClusterRoles { roleBindings = append(roleBindings, r.ownerClusterRoleBindings(owner, clusterRoleName)) } } diff --git a/controllers/tenant/utils.go b/controllers/tenant/utils.go index bca8b446..5adf7ac4 100644 --- a/controllers/tenant/utils.go +++ b/controllers/tenant/utils.go @@ -14,7 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - capsulev1beta1 "github.com/clastix/capsule/pkg/utils" + capsulev1beta2 "github.com/clastix/capsule/pkg/utils" ) // pruningResources is taking care of removing the no more requested sub-resources as LimitRange, ResourceQuota or @@ -22,7 +22,7 @@ import ( func (r *Manager) pruningResources(ctx context.Context, ns string, keys []string, obj client.Object) (err error) { var capsuleLabel string - if capsuleLabel, err = capsulev1beta1.GetTypeLabel(obj); err != nil { + if capsuleLabel, err = capsulev1beta2.GetTypeLabel(obj); err != nil { return } diff --git a/main.go b/main.go index fd713ac3..41bf4efb 100644 --- a/main.go +++ b/main.go @@ -206,7 +206,17 @@ func main() { } if err = (&capsulev1alpha1.Tenant{}).SetupWebhookWithManager(manager); err != nil { - setupLog.Error(err, "unable to create conversion webhook", "webhook", "Tenant") + setupLog.Error(err, "unable to create conversion webhook", "webhook", "capsulev1alpha1.Tenant") + os.Exit(1) + } + + if err = (&capsulev1alpha1.CapsuleConfiguration{}).SetupWebhookWithManager(manager); err != nil { + setupLog.Error(err, "unable to create conversion webhook", "webhook", "capsulev1alpha1.CapsuleConfiguration") + os.Exit(1) + } + + if err = (&capsulev1beta1.Tenant{}).SetupWebhookWithManager(manager); err != nil { + setupLog.Error(err, "unable to create conversion webhook", "webhook", "capsulev1beta1.Tenant") os.Exit(1) } diff --git a/api/v1beta1/tenant_annotations.go b/pkg/api/annotations.go similarity index 63% rename from api/v1beta1/tenant_annotations.go rename to pkg/api/annotations.go index 8c21e925..37753567 100644 --- a/api/v1beta1/tenant_annotations.go +++ b/pkg/api/annotations.go @@ -1,12 +1,7 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 -package v1beta1 - -import ( - "fmt" - "strings" -) +package api const ( ForbiddenNamespaceLabelsAnnotation = "capsule.clastix.io/forbidden-namespace-labels" @@ -15,11 +10,3 @@ const ( ForbiddenNamespaceAnnotationsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-annotations-regexp" ProtectedTenantAnnotation = "capsule.clastix.io/protected" ) - -func UsedQuotaFor(resource fmt.Stringer) string { - return "quota.capsule.clastix.io/used-" + strings.ReplaceAll(resource.String(), "/", "_") -} - -func HardQuotaFor(resource fmt.Stringer) string { - return "quota.capsule.clastix.io/hard-" + strings.ReplaceAll(resource.String(), "/", "_") -} diff --git a/pkg/configuration/client.go b/pkg/configuration/client.go index 951bde20..75ca1aa5 100644 --- a/pkg/configuration/client.go +++ b/pkg/configuration/client.go @@ -6,32 +6,30 @@ package configuration import ( "context" "regexp" - "strconv" - "strings" "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" - capsulev1beta1 "github.com/clastix/capsule/pkg/api" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + capsuleapi "github.com/clastix/capsule/pkg/api" ) // capsuleConfiguration is the Capsule Configuration retrieval mode // using a closure that provides the desired configuration. type capsuleConfiguration struct { - retrievalFn func() *capsulev1alpha1.CapsuleConfiguration + retrievalFn func() *capsulev1beta2.CapsuleConfiguration } func NewCapsuleConfiguration(ctx context.Context, client client.Client, name string) Configuration { - return &capsuleConfiguration{retrievalFn: func() *capsulev1alpha1.CapsuleConfiguration { - config := &capsulev1alpha1.CapsuleConfiguration{} + return &capsuleConfiguration{retrievalFn: func() *capsulev1beta2.CapsuleConfiguration { + config := &capsulev1beta2.CapsuleConfiguration{} if err := client.Get(ctx, types.NamespacedName{Name: name}, config); err != nil { if apierrors.IsNotFound(err) { - return &capsulev1alpha1.CapsuleConfiguration{ - Spec: capsulev1alpha1.CapsuleConfigurationSpec{ + return &capsulev1beta2.CapsuleConfiguration{ + Spec: capsulev1beta2.CapsuleConfigurationSpec{ UserGroups: []string{"capsule.clastix.io"}, ForceTenantPrefix: false, ProtectedNamespaceRegexpString: "", @@ -45,7 +43,7 @@ func NewCapsuleConfiguration(ctx context.Context, client client.Client, name str }} } -func (c capsuleConfiguration) ProtectedNamespaceRegexp() (*regexp.Regexp, error) { +func (c *capsuleConfiguration) ProtectedNamespaceRegexp() (*regexp.Regexp, error) { expr := c.retrievalFn().Spec.ProtectedNamespaceRegexpString if len(expr) == 0 { return nil, nil // nolint:nilnil @@ -59,120 +57,46 @@ func (c capsuleConfiguration) ProtectedNamespaceRegexp() (*regexp.Regexp, error) return r, nil } -func (c capsuleConfiguration) ForceTenantPrefix() bool { +func (c *capsuleConfiguration) ForceTenantPrefix() bool { return c.retrievalFn().Spec.ForceTenantPrefix } -func (c capsuleConfiguration) TLSSecretName() (name string) { - name = TLSSecretName - - if c.retrievalFn().Annotations == nil { - return - } - - v, ok := c.retrievalFn().Annotations[capsulev1alpha1.TLSSecretNameAnnotation] - if ok { - return v - } - - return +func (c *capsuleConfiguration) TLSSecretName() (name string) { + return c.retrievalFn().Spec.CapsuleResources.TLSSecretName } -func (c capsuleConfiguration) EnableTLSConfiguration() bool { - annotationValue, ok := c.retrievalFn().Annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName] - - if ok { - value, err := strconv.ParseBool(annotationValue) - if err != nil { - return false - } - - return value - } - - return false +func (c *capsuleConfiguration) EnableTLSConfiguration() bool { + return c.retrievalFn().Spec.EnableTLSReconciler } -func (c capsuleConfiguration) MutatingWebhookConfigurationName() (name string) { - name = MutatingWebhookConfigurationName - - if c.retrievalFn().Annotations == nil { - return - } - - v, ok := c.retrievalFn().Annotations[capsulev1alpha1.MutatingWebhookConfigurationName] - if ok { - return v - } - - return +func (c *capsuleConfiguration) MutatingWebhookConfigurationName() (name string) { + return c.retrievalFn().Spec.CapsuleResources.MutatingWebhookConfigurationName } -func (c capsuleConfiguration) TenantCRDName() string { +func (c *capsuleConfiguration) TenantCRDName() string { return TenantCRDName } -func (c capsuleConfiguration) ValidatingWebhookConfigurationName() (name string) { - name = ValidatingWebhookConfigurationName - - if c.retrievalFn().Annotations == nil { - return - } - - v, ok := c.retrievalFn().Annotations[capsulev1alpha1.ValidatingWebhookConfigurationName] - if ok { - return v - } - - return +func (c *capsuleConfiguration) ValidatingWebhookConfigurationName() (name string) { + return c.retrievalFn().Spec.CapsuleResources.ValidatingWebhookConfigurationName } -func (c capsuleConfiguration) UserGroups() []string { +func (c *capsuleConfiguration) UserGroups() []string { return c.retrievalFn().Spec.UserGroups } -func (c capsuleConfiguration) hasForbiddenNodeLabelsAnnotations() bool { - if _, ok := c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation]; ok { - return true - } - - if _, ok := c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation]; ok { - return true - } - - return false -} - -func (c capsuleConfiguration) hasForbiddenNodeAnnotationsAnnotations() bool { - if _, ok := c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation]; ok { - return true - } - - if _, ok := c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation]; ok { - return true - } - - return false -} - -func (c *capsuleConfiguration) ForbiddenUserNodeLabels() *capsulev1beta1.ForbiddenListSpec { - if !c.hasForbiddenNodeLabelsAnnotations() { +func (c *capsuleConfiguration) ForbiddenUserNodeLabels() *capsuleapi.ForbiddenListSpec { + if c.retrievalFn().Spec.NodeMetadata == nil { return nil } - return &capsulev1beta1.ForbiddenListSpec{ - Exact: strings.Split(c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation], ","), - Regex: c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation], - } + return &c.retrievalFn().Spec.NodeMetadata.ForbiddenLabels } -func (c *capsuleConfiguration) ForbiddenUserNodeAnnotations() *capsulev1beta1.ForbiddenListSpec { - if !c.hasForbiddenNodeAnnotationsAnnotations() { +func (c *capsuleConfiguration) ForbiddenUserNodeAnnotations() *capsuleapi.ForbiddenListSpec { + if c.retrievalFn().Spec.NodeMetadata == nil { return nil } - return &capsulev1beta1.ForbiddenListSpec{ - Exact: strings.Split(c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation], ","), - Regex: c.retrievalFn().Annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation], - } + return &c.retrievalFn().Spec.NodeMetadata.ForbiddenAnnotations } diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index f36ec723..68ad6416 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -6,14 +6,11 @@ package configuration import ( "regexp" - capsulev1beta1 "github.com/clastix/capsule/pkg/api" + capsuleapi "github.com/clastix/capsule/pkg/api" ) const ( - TLSSecretName = "capsule-tls" - MutatingWebhookConfigurationName = "capsule-mutating-webhook-configuration" - ValidatingWebhookConfigurationName = "capsule-validating-webhook-configuration" - TenantCRDName = "tenants.capsule.clastix.io" + TenantCRDName = "tenants.capsule.clastix.io" ) type Configuration interface { @@ -27,6 +24,6 @@ type Configuration interface { ValidatingWebhookConfigurationName() string TenantCRDName() string UserGroups() []string - ForbiddenUserNodeLabels() *capsulev1beta1.ForbiddenListSpec - ForbiddenUserNodeAnnotations() *capsulev1beta1.ForbiddenListSpec + ForbiddenUserNodeLabels() *capsuleapi.ForbiddenListSpec + ForbiddenUserNodeAnnotations() *capsuleapi.ForbiddenListSpec } diff --git a/pkg/indexer/indexer.go b/pkg/indexer/indexer.go index a80c7497..4dde3501 100644 --- a/pkg/indexer/indexer.go +++ b/pkg/indexer/indexer.go @@ -16,7 +16,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/indexer/ingress" "github.com/clastix/capsule/pkg/indexer/namespace" @@ -31,7 +30,6 @@ type CustomIndexer interface { func AddToManager(ctx context.Context, log logr.Logger, mgr manager.Manager) error { indexers := []CustomIndexer{ - tenant.NamespacesReference{Obj: &capsulev1beta1.Tenant{}}, tenant.NamespacesReference{Obj: &capsulev1beta2.Tenant{}}, tenant.OwnerReference{}, namespace.OwnerReference{}, diff --git a/pkg/indexer/namespace/namespaces.go b/pkg/indexer/namespace/namespaces.go index 52f9aa4d..87ea803b 100644 --- a/pkg/indexer/namespace/namespaces.go +++ b/pkg/indexer/namespace/namespaces.go @@ -9,7 +9,7 @@ import ( corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) type OwnerReference struct{} @@ -32,7 +32,7 @@ func (o OwnerReference) Func() client.IndexerFunc { } for _, or := range ns.OwnerReferences { - if or.APIVersion == capsulev1beta1.GroupVersion.String() { + if or.APIVersion == capsulev1beta2.GroupVersion.String() { res = append(res, or.Name) } } diff --git a/pkg/indexer/tenant/owner.go b/pkg/indexer/tenant/owner.go index f947d66d..46dc3c69 100644 --- a/pkg/indexer/tenant/owner.go +++ b/pkg/indexer/tenant/owner.go @@ -8,14 +8,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/utils" ) type OwnerReference struct{} func (o OwnerReference) Object() client.Object { - return &capsulev1beta1.Tenant{} + return &capsulev1beta2.Tenant{} } func (o OwnerReference) Field() string { @@ -24,9 +24,9 @@ func (o OwnerReference) Field() string { func (o OwnerReference) Func() client.IndexerFunc { return func(object client.Object) []string { - tenant, ok := object.(*capsulev1beta1.Tenant) + tenant, ok := object.(*capsulev1beta2.Tenant) if !ok { - panic(fmt.Errorf("expected type *capsulev1beta1.Tenant, got %T", tenant)) + panic(fmt.Errorf("expected type *capsulev1beta2.Tenant, got %T", tenant)) } return utils.GetOwnersWithKinds(tenant) diff --git a/pkg/utils/node_selector.go b/pkg/utils/node_selector.go index d7160e87..94f3b785 100644 --- a/pkg/utils/node_selector.go +++ b/pkg/utils/node_selector.go @@ -8,14 +8,14 @@ import ( "sort" "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) const ( NodeSelectorAnnotation = "scheduler.alpha.kubernetes.io/node-selector" ) -func BuildNodeSelector(tnt *capsulev1beta1.Tenant, nsAnnotations map[string]string) map[string]string { +func BuildNodeSelector(tnt *capsulev1beta2.Tenant, nsAnnotations map[string]string) map[string]string { if nsAnnotations == nil { nsAnnotations = make(map[string]string) } diff --git a/pkg/utils/owner.go b/pkg/utils/owner.go index 6ec97f90..fad2c695 100644 --- a/pkg/utils/owner.go +++ b/pkg/utils/owner.go @@ -6,10 +6,10 @@ package utils import ( "fmt" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) -func GetOwnersWithKinds(tenant *capsulev1beta1.Tenant) (owners []string) { +func GetOwnersWithKinds(tenant *capsulev1beta2.Tenant) (owners []string) { for _, owner := range tenant.Spec.Owners { owners = append(owners, fmt.Sprintf("%s:%s", owner.Kind.String(), owner.Name)) } diff --git a/pkg/webhook/ingress/utils.go b/pkg/webhook/ingress/utils.go index 52850a81..c9185b93 100644 --- a/pkg/webhook/ingress/utils.go +++ b/pkg/webhook/ingress/utils.go @@ -14,11 +14,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) -func tenantFromIngress(ctx context.Context, c client.Client, ingress Ingress) (*capsulev1beta1.Tenant, error) { - tenantList := &capsulev1beta1.TenantList{} +func tenantFromIngress(ctx context.Context, c client.Client, ingress Ingress) (*capsulev1beta2.Tenant, error) { + tenantList := &capsulev1beta2.TenantList{} if err := c.List(ctx, tenantList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", ingress.Namespace()), }); err != nil { diff --git a/pkg/webhook/ingress/validate_class.go b/pkg/webhook/ingress/validate_class.go index c7f67c68..34f79fed 100644 --- a/pkg/webhook/ingress/validate_class.go +++ b/pkg/webhook/ingress/validate_class.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/configuration" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" @@ -34,7 +34,7 @@ func (r *class) OnCreate(client client.Client, decoder *admission.Decoder, recor return utils.ErroredResponse(err) } - var tenant *capsulev1beta1.Tenant + var tenant *capsulev1beta2.Tenant tenant, err = tenantFromIngress(ctx, client, ingress) if err != nil { @@ -75,7 +75,7 @@ func (r *class) OnUpdate(client client.Client, decoder *admission.Decoder, recor return utils.ErroredResponse(err) } - var tenant *capsulev1beta1.Tenant + var tenant *capsulev1beta2.Tenant tenant, err = tenantFromIngress(ctx, client, ingress) if err != nil { @@ -114,7 +114,7 @@ func (r *class) OnDelete(client.Client, *admission.Decoder, record.EventRecorder } } -func (r *class) validateClass(tenant capsulev1beta1.Tenant, ingressClass *string) error { +func (r *class) validateClass(tenant capsulev1beta2.Tenant, ingressClass *string) error { if tenant.Spec.IngressOptions.AllowedClasses == nil { return nil } diff --git a/pkg/webhook/ingress/validate_collision.go b/pkg/webhook/ingress/validate_collision.go index f7c508d4..dd9a73b6 100644 --- a/pkg/webhook/ingress/validate_collision.go +++ b/pkg/webhook/ingress/validate_collision.go @@ -18,7 +18,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" "github.com/clastix/capsule/pkg/configuration" "github.com/clastix/capsule/pkg/indexer/ingress" @@ -42,7 +42,7 @@ func (r *collision) OnCreate(client client.Client, decoder *admission.Decoder, r return utils.ErroredResponse(err) } - var tenant *capsulev1beta1.Tenant + var tenant *capsulev1beta2.Tenant tenant, err = tenantFromIngress(ctx, client, ing) if err != nil { @@ -77,7 +77,7 @@ func (r *collision) OnUpdate(client client.Client, decoder *admission.Decoder, r return utils.ErroredResponse(err) } - var tenant *capsulev1beta1.Tenant + var tenant *capsulev1beta2.Tenant tenant, err = tenantFromIngress(ctx, client, ing) if err != nil { @@ -129,7 +129,7 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in // nolint:exhaustive switch scope { case api.HostnameCollisionScopeCluster: - tenantList := &capsulev1beta1.TenantList{} + tenantList := &capsulev1beta2.TenantList{} if err := clt.List(ctx, tenantList); err != nil { return err } @@ -140,7 +140,7 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in case api.HostnameCollisionScopeTenant: selector := client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", ing.Namespace())} - tenantList := &capsulev1beta1.TenantList{} + tenantList := &capsulev1beta2.TenantList{} if err := clt.List(ctx, tenantList, selector); err != nil { return err } diff --git a/pkg/webhook/ingress/validate_hostnames.go b/pkg/webhook/ingress/validate_hostnames.go index 77df4865..84c69af5 100644 --- a/pkg/webhook/ingress/validate_hostnames.go +++ b/pkg/webhook/ingress/validate_hostnames.go @@ -14,7 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/configuration" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" @@ -35,7 +35,7 @@ func (r *hostnames) OnCreate(c client.Client, decoder *admission.Decoder, record return utils.ErroredResponse(err) } - var tenant *capsulev1beta1.Tenant + var tenant *capsulev1beta2.Tenant tenant, err = tenantFromIngress(ctx, c, ingress) if err != nil { @@ -76,7 +76,7 @@ func (r *hostnames) OnUpdate(c client.Client, decoder *admission.Decoder, record return utils.ErroredResponse(err) } - var tenant *capsulev1beta1.Tenant + var tenant *capsulev1beta2.Tenant tenant, err = tenantFromIngress(ctx, c, ingress) if err != nil { @@ -116,7 +116,7 @@ func (r *hostnames) OnDelete(client.Client, *admission.Decoder, record.EventReco } } -func (r *hostnames) validateHostnames(tenant capsulev1beta1.Tenant, hostnames sets.String) error { +func (r *hostnames) validateHostnames(tenant capsulev1beta2.Tenant, hostnames sets.String) error { if tenant.Spec.IngressOptions.AllowedHostnames == nil { return nil } diff --git a/pkg/webhook/ingress/validate_wildcard.go b/pkg/webhook/ingress/validate_wildcard.go index a865ae77..1cc9a4a3 100644 --- a/pkg/webhook/ingress/validate_wildcard.go +++ b/pkg/webhook/ingress/validate_wildcard.go @@ -14,7 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -44,7 +44,7 @@ func (h *wildcard) OnUpdate(client client.Client, decoder *admission.Decoder, re } func (h *wildcard) wildcardHandler(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder, decoder *admission.Decoder) *admission.Response { - tntList := &capsulev1beta1.TenantList{} + tntList := &capsulev1beta2.TenantList{} if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace), @@ -59,8 +59,7 @@ func (h *wildcard) wildcardHandler(ctx context.Context, clt client.Client, req a tnt := tntList.Items[0] - // Check if Annotation in manifest has value "capsule.clastix.io/deny-wildcard" set to "true". - if tnt.IsWildcardDenied() { + if !tnt.Spec.IngressOptions.AllowWildcardHostnames { // Retrieve ingress resource from request. ingress, err := ingressFromRequest(req, decoder) if err != nil { diff --git a/pkg/webhook/namespace/errors.go b/pkg/webhook/namespace/errors.go index 04e87804..e28812e0 100644 --- a/pkg/webhook/namespace/errors.go +++ b/pkg/webhook/namespace/errors.go @@ -7,11 +7,11 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/pkg/api" + capsuleapi "github.com/clastix/capsule/pkg/api" ) // nolint:predeclared -func appendForbiddenError(spec *capsulev1beta1.ForbiddenListSpec) (append string) { +func appendForbiddenError(spec *capsuleapi.ForbiddenListSpec) (append string) { append += "Forbidden are " if len(spec.Exact) > 0 { append += fmt.Sprintf("one of the following (%s)", strings.Join(spec.Exact, ", ")) @@ -39,10 +39,10 @@ func (namespaceQuotaExceededError) Error() string { type namespaceLabelForbiddenError struct { label string - spec *capsulev1beta1.ForbiddenListSpec + spec *capsuleapi.ForbiddenListSpec } -func NewNamespaceLabelForbiddenError(label string, forbiddenSpec *capsulev1beta1.ForbiddenListSpec) error { +func NewNamespaceLabelForbiddenError(label string, forbiddenSpec *capsuleapi.ForbiddenListSpec) error { return &namespaceLabelForbiddenError{ label: label, spec: forbiddenSpec, @@ -55,10 +55,10 @@ func (f namespaceLabelForbiddenError) Error() string { type namespaceAnnotationForbiddenError struct { annotation string - spec *capsulev1beta1.ForbiddenListSpec + spec *capsuleapi.ForbiddenListSpec } -func NewNamespaceAnnotationForbiddenError(annotation string, forbiddenSpec *capsulev1beta1.ForbiddenListSpec) error { +func NewNamespaceAnnotationForbiddenError(annotation string, forbiddenSpec *capsuleapi.ForbiddenListSpec) error { return &namespaceAnnotationForbiddenError{ annotation: annotation, spec: forbiddenSpec, diff --git a/pkg/webhook/namespace/freezed.go b/pkg/webhook/namespace/freezed.go index 290217a4..a9a1234d 100644 --- a/pkg/webhook/namespace/freezed.go +++ b/pkg/webhook/namespace/freezed.go @@ -13,7 +13,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/configuration" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" @@ -36,12 +36,12 @@ func (r *freezedHandler) OnCreate(client client.Client, decoder *admission.Decod for _, objectRef := range ns.ObjectMeta.OwnerReferences { // retrieving the selected Tenant - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil { return utils.ErroredResponse(err) } - if tnt.IsCordoned() { + if tnt.Spec.Cordoned { recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be attached, the current Tenant is freezed", ns.GetName()) response := admission.Denied("the selected Tenant is freezed") @@ -56,7 +56,7 @@ func (r *freezedHandler) OnCreate(client client.Client, decoder *admission.Decod func (r *freezedHandler) OnDelete(c client.Client, _ *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - tntList := &capsulev1beta1.TenantList{} + tntList := &capsulev1beta2.TenantList{} if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", req.Name), }); err != nil { @@ -69,7 +69,7 @@ func (r *freezedHandler) OnDelete(c client.Client, _ *admission.Decoder, recorde tnt := tntList.Items[0] - if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups()) { + if tnt.Spec.Cordoned && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups()) { recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be deleted, the current Tenant is freezed", req.Name) response := admission.Denied("the selected Tenant is freezed") @@ -88,7 +88,7 @@ func (r *freezedHandler) OnUpdate(c client.Client, decoder *admission.Decoder, r return utils.ErroredResponse(err) } - tntList := &capsulev1beta1.TenantList{} + tntList := &capsulev1beta2.TenantList{} if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", ns.Name), }); err != nil { @@ -101,7 +101,7 @@ func (r *freezedHandler) OnUpdate(c client.Client, decoder *admission.Decoder, r tnt := tntList.Items[0] - if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups()) { + if tnt.Spec.Cordoned && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserGroups()) { recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be updated, the current Tenant is freezed", ns.GetName()) response := admission.Denied("the selected Tenant is freezed") diff --git a/pkg/webhook/namespace/patch.go b/pkg/webhook/namespace/patch.go index 23dcaeb6..18f1e972 100644 --- a/pkg/webhook/namespace/patch.go +++ b/pkg/webhook/namespace/patch.go @@ -14,8 +14,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" - utils2 "github.com/clastix/capsule/pkg/utils" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + capsuleutils "github.com/clastix/capsule/pkg/utils" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -47,7 +47,7 @@ func (r *patchHandler) OnUpdate(c client.Client, decoder *admission.Decoder, rec } // Get Tenant Label - ln, err := utils2.GetTypeLabel(&capsulev1beta1.Tenant{}) + ln, err := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{}) if err != nil { response := admission.Errored(http.StatusBadRequest, err) @@ -59,7 +59,7 @@ func (r *patchHandler) OnUpdate(c client.Client, decoder *admission.Decoder, rec if label, ok := ns.ObjectMeta.Labels[ln]; ok { // retrieving the selected Tenant - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} if err = c.Get(ctx, types.NamespacedName{Name: label}, tnt); err != nil { response := admission.Errored(http.StatusBadRequest, err) diff --git a/pkg/webhook/namespace/prefix.go b/pkg/webhook/namespace/prefix.go index 14cf74d2..7c998ba6 100644 --- a/pkg/webhook/namespace/prefix.go +++ b/pkg/webhook/namespace/prefix.go @@ -14,7 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/configuration" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" @@ -46,7 +46,7 @@ func (r *prefixHandler) OnCreate(clt client.Client, decoder *admission.Decoder, } if r.configuration.ForceTenantPrefix() { - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} for _, or := range ns.ObjectMeta.OwnerReferences { // retrieving the selected Tenant diff --git a/pkg/webhook/namespace/quota.go b/pkg/webhook/namespace/quota.go index 0517c7e9..86010e86 100644 --- a/pkg/webhook/namespace/quota.go +++ b/pkg/webhook/namespace/quota.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -32,7 +32,7 @@ func (r *quotaHandler) OnCreate(client client.Client, decoder *admission.Decoder for _, objectRef := range ns.ObjectMeta.OwnerReferences { // retrieving the selected Tenant - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/namespace/user_metadata.go b/pkg/webhook/namespace/user_metadata.go index b4dc3489..2a90df1e 100644 --- a/pkg/webhook/namespace/user_metadata.go +++ b/pkg/webhook/namespace/user_metadata.go @@ -13,7 +13,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -24,9 +24,9 @@ func UserMetadataHandler() capsulewebhook.Handler { return &userMetadataHandler{} } -func (r *userMetadataHandler) validateUserMetadata(tnt *capsulev1beta1.Tenant, recorder record.EventRecorder, labels map[string]string, annotations map[string]string) *admission.Response { - if tnt.ForbiddenUserNamespaceLabels() != nil { - forbiddenLabels := tnt.ForbiddenUserNamespaceLabels() +func (r *userMetadataHandler) validateUserMetadata(tnt *capsulev1beta2.Tenant, recorder record.EventRecorder, labels map[string]string, annotations map[string]string) *admission.Response { + if tnt.Spec.NamespaceOptions != nil { + forbiddenLabels := tnt.Spec.NamespaceOptions.ForbiddenLabels for label := range labels { var forbidden, matched bool @@ -36,28 +36,30 @@ func (r *userMetadataHandler) validateUserMetadata(tnt *capsulev1beta1.Tenant, r if forbidden || matched { recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNamespaceLabel", fmt.Sprintf("Label %s is forbidden for a namespaces of the current Tenant ", label)) - response := admission.Denied(NewNamespaceLabelForbiddenError(label, forbiddenLabels).Error()) + response := admission.Denied(NewNamespaceLabelForbiddenError(label, &forbiddenLabels).Error()) return &response } } } - if tnt.ForbiddenUserNamespaceAnnotations() != nil { - forbiddenAnnotations := tnt.ForbiddenUserNamespaceLabels() + if tnt.Spec.NamespaceOptions == nil { + return nil + } - for annotation := range annotations { - var forbidden, matched bool - forbidden = forbiddenAnnotations.ExactMatch(annotation) - matched = forbiddenAnnotations.RegexMatch(annotation) + forbiddenAnnotations := tnt.Spec.NamespaceOptions.ForbiddenLabels - if forbidden || matched { - recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNamespaceAnnotation", fmt.Sprintf("Annotation %s is forbidden for a namespaces of the current Tenant ", annotation)) + for annotation := range annotations { + var forbidden, matched bool + forbidden = forbiddenAnnotations.ExactMatch(annotation) + matched = forbiddenAnnotations.RegexMatch(annotation) - response := admission.Denied(NewNamespaceAnnotationForbiddenError(annotation, forbiddenAnnotations).Error()) + if forbidden || matched { + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNamespaceAnnotation", fmt.Sprintf("Annotation %s is forbidden for a namespaces of the current Tenant ", annotation)) - return &response - } + response := admission.Denied(NewNamespaceAnnotationForbiddenError(annotation, &forbiddenAnnotations).Error()) + + return &response } } @@ -71,7 +73,7 @@ func (r *userMetadataHandler) OnCreate(client client.Client, decoder *admission. return utils.ErroredResponse(err) } - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} for _, objectRef := range ns.ObjectMeta.OwnerReferences { // retrieving the selected Tenant if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil { @@ -104,7 +106,7 @@ func (r *userMetadataHandler) OnUpdate(client client.Client, decoder *admission. return utils.ErroredResponse(err) } - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} for _, objectRef := range newNs.ObjectMeta.OwnerReferences { // retrieving the selected Tenant if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil { diff --git a/pkg/webhook/networkpolicy/validating.go b/pkg/webhook/networkpolicy/validating.go index 35d01507..c63d65ed 100644 --- a/pkg/webhook/networkpolicy/validating.go +++ b/pkg/webhook/networkpolicy/validating.go @@ -13,8 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" - utils2 "github.com/clastix/capsule/pkg/utils" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + capsuleutils "github.com/clastix/capsule/pkg/utils" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -31,7 +31,7 @@ func (r *handler) OnCreate(client.Client, *admission.Decoder, record.EventRecord } } -func (r *handler) generic(ctx context.Context, req admission.Request, client client.Client, _ *admission.Decoder) (*capsulev1beta1.Tenant, error) { +func (r *handler) generic(ctx context.Context, req admission.Request, client client.Client, _ *admission.Decoder) (*capsulev1beta2.Tenant, error) { var err error np := &networkingv1.NetworkPolicy{} @@ -39,9 +39,9 @@ func (r *handler) generic(ctx context.Context, req admission.Request, client cli return nil, err } - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} - l, _ := utils2.GetTypeLabel(&capsulev1beta1.Tenant{}) + l, _ := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{}) if v, ok := np.GetLabels()[l]; ok { if err = client.Get(ctx, types.NamespacedName{Name: v}, tnt); err != nil { return nil, err diff --git a/pkg/webhook/node/errors.go b/pkg/webhook/node/errors.go index e8435a78..2bd22a85 100644 --- a/pkg/webhook/node/errors.go +++ b/pkg/webhook/node/errors.go @@ -7,11 +7,11 @@ import ( "fmt" "strings" - capsulev1beta1 "github.com/clastix/capsule/pkg/api" + capsulev1beta2 "github.com/clastix/capsule/pkg/api" ) // nolint:predeclared -func appendForbiddenError(spec *capsulev1beta1.ForbiddenListSpec) (append string) { +func appendForbiddenError(spec *capsulev1beta2.ForbiddenListSpec) (append string) { append += "Forbidden are " if len(spec.Exact) > 0 { append += fmt.Sprintf("one of the following (%s)", strings.Join(spec.Exact, ", ")) @@ -28,10 +28,10 @@ func appendForbiddenError(spec *capsulev1beta1.ForbiddenListSpec) (append string } type nodeLabelForbiddenError struct { - spec *capsulev1beta1.ForbiddenListSpec + spec *capsulev1beta2.ForbiddenListSpec } -func NewNodeLabelForbiddenError(forbiddenSpec *capsulev1beta1.ForbiddenListSpec) error { +func NewNodeLabelForbiddenError(forbiddenSpec *capsulev1beta2.ForbiddenListSpec) error { return &nodeLabelForbiddenError{ spec: forbiddenSpec, } @@ -42,10 +42,10 @@ func (f nodeLabelForbiddenError) Error() string { } type nodeAnnotationForbiddenError struct { - spec *capsulev1beta1.ForbiddenListSpec + spec *capsulev1beta2.ForbiddenListSpec } -func NewNodeAnnotationForbiddenError(forbiddenSpec *capsulev1beta1.ForbiddenListSpec) error { +func NewNodeAnnotationForbiddenError(forbiddenSpec *capsulev1beta2.ForbiddenListSpec) error { return &nodeAnnotationForbiddenError{ spec: forbiddenSpec, } diff --git a/pkg/webhook/ownerreference/patching.go b/pkg/webhook/ownerreference/patching.go index 2a39aba7..fb950cba 100644 --- a/pkg/webhook/ownerreference/patching.go +++ b/pkg/webhook/ownerreference/patching.go @@ -19,9 +19,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/configuration" - utils2 "github.com/clastix/capsule/pkg/utils" + capsuleutils "github.com/clastix/capsule/pkg/utils" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -62,7 +62,7 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client return &response } - ln, err := utils2.GetTypeLabel(&capsulev1beta1.Tenant{}) + ln, err := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{}) if err != nil { response := admission.Errored(http.StatusBadRequest, err) @@ -71,7 +71,7 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client // If we already had TenantName label on NS -> assign to it if label, ok := ns.ObjectMeta.Labels[ln]; ok { // retrieving the selected Tenant - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} if err = client.Get(ctx, types.NamespacedName{Name: label}, tnt); err != nil { response := admission.Errored(http.StatusBadRequest, err) @@ -96,7 +96,7 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client // Find tenants belonging to user (it can be regular user or ServiceAccount) if strings.HasPrefix(req.UserInfo.Username, "system:serviceaccount:") { - var tntList *capsulev1beta1.TenantList + var tntList *capsulev1beta2.TenantList if tntList, err = h.listTenantsForOwnerKind(ctx, "ServiceAccount", req.UserInfo.Username, client); err != nil { response := admission.Errored(http.StatusBadRequest, err) @@ -108,7 +108,7 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client tenants = append(tenants, tnt) } } else { - var tntList *capsulev1beta1.TenantList + var tntList *capsulev1beta2.TenantList if tntList, err = h.listTenantsForOwnerKind(ctx, "User", req.UserInfo.Username, client); err != nil { response := admission.Errored(http.StatusBadRequest, err) @@ -170,9 +170,9 @@ func (h *handler) setOwnerRef(ctx context.Context, req admission.Request, client return &response } -func (h *handler) patchResponseForOwnerRef(tenant *capsulev1beta1.Tenant, ns *corev1.Namespace, recorder record.EventRecorder) admission.Response { +func (h *handler) patchResponseForOwnerRef(tenant *capsulev1beta2.Tenant, ns *corev1.Namespace, recorder record.EventRecorder) admission.Response { scheme := runtime.NewScheme() - _ = capsulev1beta1.AddToScheme(scheme) + _ = capsulev1beta2.AddToScheme(scheme) _ = corev1.AddToScheme(scheme) o, err := json.Marshal(ns.DeepCopy()) @@ -196,8 +196,8 @@ func (h *handler) patchResponseForOwnerRef(tenant *capsulev1beta1.Tenant, ns *co return admission.PatchResponseFromRaw(o, c) } -func (h *handler) listTenantsForOwnerKind(ctx context.Context, ownerKind string, ownerName string, clt client.Client) (*capsulev1beta1.TenantList, error) { - tntList := &capsulev1beta1.TenantList{} +func (h *handler) listTenantsForOwnerKind(ctx context.Context, ownerKind string, ownerName string, clt client.Client) (*capsulev1beta2.TenantList, error) { + tntList := &capsulev1beta2.TenantList{} fields := client.MatchingFields{ ".spec.owner.ownerkind": fmt.Sprintf("%s:%s", ownerKind, ownerName), } @@ -206,7 +206,7 @@ func (h *handler) listTenantsForOwnerKind(ctx context.Context, ownerKind string, return tntList, err } -type sortedTenants []capsulev1beta1.Tenant +type sortedTenants []capsulev1beta2.Tenant func (s sortedTenants) Len() int { return len(s) diff --git a/pkg/webhook/pod/containerregistry.go b/pkg/webhook/pod/containerregistry.go index fbb11af8..9f576a94 100644 --- a/pkg/webhook/pod/containerregistry.go +++ b/pkg/webhook/pod/containerregistry.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -30,7 +30,7 @@ func (h *containerRegistryHandler) OnCreate(c client.Client, decoder *admission. return utils.ErroredResponse(err) } - tntList := &capsulev1beta1.TenantList{} + tntList := &capsulev1beta2.TenantList{} if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace), }); err != nil { diff --git a/pkg/webhook/pod/imagepullpolicy.go b/pkg/webhook/pod/imagepullpolicy.go index f1ed80d4..3b160b20 100644 --- a/pkg/webhook/pod/imagepullpolicy.go +++ b/pkg/webhook/pod/imagepullpolicy.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -30,7 +30,7 @@ func (r *imagePullPolicy) OnCreate(c client.Client, decoder *admission.Decoder, return utils.ErroredResponse(err) } - tntList := &capsulev1beta1.TenantList{} + tntList := &capsulev1beta2.TenantList{} if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace), }); err != nil { diff --git a/pkg/webhook/pod/imagepullpolicy_pullpolicy.go b/pkg/webhook/pod/imagepullpolicy_pullpolicy.go index 884c250f..ccafe549 100644 --- a/pkg/webhook/pod/imagepullpolicy_pullpolicy.go +++ b/pkg/webhook/pod/imagepullpolicy_pullpolicy.go @@ -6,7 +6,7 @@ package pod import ( "strings" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) type PullPolicy interface { @@ -32,7 +32,7 @@ func (i imagePullPolicyValidator) AllowedPullPolicies() []string { return i.allowedPolicies } -func NewPullPolicy(tenant *capsulev1beta1.Tenant) PullPolicy { +func NewPullPolicy(tenant *capsulev1beta2.Tenant) PullPolicy { // the Tenant doesn't enforce the allowed image pull policy, returning nil if len(tenant.Spec.ImagePullPolicies) == 0 { return nil diff --git a/pkg/webhook/pod/priorityclass.go b/pkg/webhook/pod/priorityclass.go index 3bd7bd5e..7a2515d0 100644 --- a/pkg/webhook/pod/priorityclass.go +++ b/pkg/webhook/pod/priorityclass.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -30,7 +30,7 @@ func (h *priorityClass) OnCreate(c client.Client, decoder *admission.Decoder, re return utils.ErroredResponse(err) } - tntList := &capsulev1beta1.TenantList{} + tntList := &capsulev1beta2.TenantList{} if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace), diff --git a/pkg/webhook/pvc/validating.go b/pkg/webhook/pvc/validating.go index 579a47f8..8a54bfa8 100644 --- a/pkg/webhook/pvc/validating.go +++ b/pkg/webhook/pvc/validating.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -32,7 +32,7 @@ func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder return utils.ErroredResponse(err) } - tntList := &capsulev1beta1.TenantList{} + tntList := &capsulev1beta2.TenantList{} if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", pvc.Namespace), }); err != nil { diff --git a/pkg/webhook/route/tenants.go b/pkg/webhook/route/tenants.go index 63552ffe..8059c8f7 100644 --- a/pkg/webhook/route/tenants.go +++ b/pkg/webhook/route/tenants.go @@ -7,7 +7,7 @@ import ( capsulewebhook "github.com/clastix/capsule/pkg/webhook" ) -// +kubebuilder:webhook:path=/tenants,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="capsule.clastix.io",resources=tenants,verbs=create;update;delete,versions=v1beta1,name=tenants.capsule.clastix.io +// +kubebuilder:webhook:path=/tenants,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="capsule.clastix.io",resources=tenants,verbs=create;update;delete,versions=v1beta2,name=tenants.capsule.clastix.io type tenant struct { handlers []capsulewebhook.Handler diff --git a/pkg/webhook/service/validating.go b/pkg/webhook/service/validating.go index 1de66e2e..3dda98bb 100644 --- a/pkg/webhook/service/validating.go +++ b/pkg/webhook/service/validating.go @@ -14,7 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -31,7 +31,7 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder return utils.ErroredResponse(err) } - tntList := &capsulev1beta1.TenantList{} + tntList := &capsulev1beta2.TenantList{} if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", svc.GetNamespace()), }); err != nil { diff --git a/pkg/webhook/tenant/containerregistry_regex.go b/pkg/webhook/tenant/containerregistry_regex.go index 41e490e7..2445e896 100644 --- a/pkg/webhook/tenant/containerregistry_regex.go +++ b/pkg/webhook/tenant/containerregistry_regex.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -24,7 +24,7 @@ func ContainerRegistryRegexHandler() capsulewebhook.Handler { } func (h *containerRegistryRegexHandler) validate(decoder *admission.Decoder, req admission.Request) *admission.Response { - tenant := &capsulev1beta1.Tenant{} + tenant := &capsulev1beta2.Tenant{} if err := decoder.Decode(req, tenant); err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/tenant/cordoning.go b/pkg/webhook/tenant/cordoning.go index cc024ac7..13b6a3f8 100644 --- a/pkg/webhook/tenant/cordoning.go +++ b/pkg/webhook/tenant/cordoning.go @@ -14,7 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/configuration" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" @@ -31,7 +31,7 @@ func CordoningHandler(configuration configuration.Configuration) capsulewebhook. } func (h *cordoningHandler) cordonHandler(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder) *admission.Response { - tntList := &capsulev1beta1.TenantList{} + tntList := &capsulev1beta2.TenantList{} if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace), @@ -44,7 +44,7 @@ func (h *cordoningHandler) cordonHandler(ctx context.Context, clt client.Client, } tnt := tntList.Items[0] - if tnt.IsCordoned() && utils.IsCapsuleUser(ctx, req, clt, h.configuration.UserGroups()) { + if tnt.Spec.Cordoned && utils.IsCapsuleUser(ctx, req, clt, h.configuration.UserGroups()) { recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "%s %s/%s cannot be %sd, current Tenant is freezed", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation))) response := admission.Denied(fmt.Sprintf("tenant %s is freezed: please, reach out to the system administrator", tnt.GetName())) diff --git a/pkg/webhook/tenant/custom_resource_quota.go b/pkg/webhook/tenant/custom_resource_quota.go index e997e1de..d8cbd7cc 100644 --- a/pkg/webhook/tenant/custom_resource_quota.go +++ b/pkg/webhook/tenant/custom_resource_quota.go @@ -16,7 +16,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -36,7 +36,7 @@ func ResourceCounterHandler() capsulewebhook.Handler { } func (r *resourceCounterHandler) getTenantName(ctx context.Context, clt client.Client, req admission.Request) (string, error) { - tntList := &capsulev1beta1.TenantList{} + tntList := &capsulev1beta2.TenantList{} if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace), @@ -67,7 +67,7 @@ func (r *resourceCounterHandler) OnCreate(clt client.Client, decoder *admission. kgv := fmt.Sprintf("%s.%s_%s", req.Resource.Resource, req.Resource.Group, req.Resource.Version) - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} var limit int64 @@ -76,20 +76,20 @@ func (r *resourceCounterHandler) OnCreate(clt client.Client, decoder *admission. return retryErr } - if limit, retryErr = capsulev1beta1.GetLimitResourceFromTenant(*tnt, kgv); retryErr != nil { - if errors.As(err, &capsulev1beta1.NonLimitedResourceError{}) { + if limit, retryErr = capsulev1beta2.GetLimitResourceFromTenant(*tnt, kgv); retryErr != nil { + if errors.As(err, &capsulev1beta2.NonLimitedResourceError{}) { return nil } return err } - used, _ := capsulev1beta1.GetUsedResourceFromTenant(*tnt, kgv) + used, _ := capsulev1beta2.GetUsedResourceFromTenant(*tnt, kgv) if used >= limit { return NewCustomResourceQuotaError(kgv, limit) } - tnt.Annotations[capsulev1beta1.UsedAnnotationForResource(kgv)] = fmt.Sprintf("%d", used+1) + tnt.Annotations[capsulev1beta2.UsedAnnotationForResource(kgv)] = fmt.Sprintf("%d", used+1) return clt.Update(ctx, tnt) }) @@ -122,7 +122,7 @@ func (r *resourceCounterHandler) OnDelete(clt client.Client, decoder *admission. kgv := fmt.Sprintf("%s.%s_%s", req.Resource.Resource, req.Resource.Group, req.Resource.Version) err = retry.RetryOnConflict(retry.DefaultRetry, func() (retryErr error) { - tnt := &capsulev1beta1.Tenant{} + tnt := &capsulev1beta2.Tenant{} if retryErr = clt.Get(ctx, types.NamespacedName{Name: tntName}, tnt); err != nil { return } @@ -131,13 +131,13 @@ func (r *resourceCounterHandler) OnDelete(clt client.Client, decoder *admission. return } - if _, ok := tnt.Annotations[capsulev1beta1.UsedAnnotationForResource(kgv)]; !ok { + if _, ok := tnt.Annotations[capsulev1beta2.UsedAnnotationForResource(kgv)]; !ok { return } - used, _ := capsulev1beta1.GetUsedResourceFromTenant(*tnt, kgv) + used, _ := capsulev1beta2.GetUsedResourceFromTenant(*tnt, kgv) - tnt.Annotations[capsulev1beta1.UsedAnnotationForResource(kgv)] = fmt.Sprintf("%d", used-1) + tnt.Annotations[capsulev1beta2.UsedAnnotationForResource(kgv)] = fmt.Sprintf("%d", used-1) return clt.Update(ctx, tnt) }) diff --git a/pkg/webhook/tenant/forbidden_annotations_regex.go b/pkg/webhook/tenant/forbidden_annotations_regex.go index 61f47c2e..3cfb79f2 100644 --- a/pkg/webhook/tenant/forbidden_annotations_regex.go +++ b/pkg/webhook/tenant/forbidden_annotations_regex.go @@ -5,13 +5,14 @@ package tenant import ( "context" + "fmt" "regexp" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -23,23 +24,23 @@ func ForbiddenAnnotationsRegexHandler() capsulewebhook.Handler { } func (h *forbiddenAnnotationsRegexHandler) validate(decoder *admission.Decoder, req admission.Request) *admission.Response { - tenant := &capsulev1beta1.Tenant{} + tenant := &capsulev1beta2.Tenant{} if err := decoder.Decode(req, tenant); err != nil { return utils.ErroredResponse(err) } - if tenant.Annotations == nil { + if tenant.Spec.NamespaceOptions == nil { return nil } - annotationsToCheck := []string{ - capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation, - capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation, + annotationsToCheck := map[string]string{ + "labels": tenant.Spec.NamespaceOptions.ForbiddenLabels.Regex, + "annotations": tenant.Spec.NamespaceOptions.ForbiddenAnnotations.Regex, } - for _, annotation := range annotationsToCheck { - if _, err := regexp.Compile(tenant.Annotations[annotation]); err != nil { - response := admission.Denied("unable to compile " + annotation + " regex annotation") + for scope, annotation := range annotationsToCheck { + if _, err := regexp.Compile(tenant.Spec.NamespaceOptions.ForbiddenLabels.Regex); err != nil { + response := admission.Denied(fmt.Sprintf("unable to compile %s regex for forbidden %s", annotation, scope)) return &response } diff --git a/pkg/webhook/tenant/freezed_emitter.go b/pkg/webhook/tenant/freezed_emitter.go index 7a7a627e..a4c2d228 100644 --- a/pkg/webhook/tenant/freezed_emitter.go +++ b/pkg/webhook/tenant/freezed_emitter.go @@ -11,7 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -36,20 +36,20 @@ func (h *freezedEmitterHandler) OnDelete(client.Client, *admission.Decoder, reco func (h *freezedEmitterHandler) OnUpdate(_ client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - oldTnt := &capsulev1beta1.Tenant{} + oldTnt := &capsulev1beta2.Tenant{} if err := decoder.DecodeRaw(req.OldObject, oldTnt); err != nil { return utils.ErroredResponse(err) } - newTnt := &capsulev1beta1.Tenant{} + newTnt := &capsulev1beta2.Tenant{} if err := decoder.Decode(req, newTnt); err != nil { return utils.ErroredResponse(err) } switch { - case !oldTnt.IsCordoned() && newTnt.IsCordoned(): + case !oldTnt.Spec.Cordoned && newTnt.Spec.Cordoned: recorder.Eventf(newTnt, corev1.EventTypeNormal, "TenantCordoned", "Tenant has been cordoned") - case oldTnt.IsCordoned() && !newTnt.IsCordoned(): + case oldTnt.Spec.Cordoned && !newTnt.Spec.Cordoned: recorder.Eventf(newTnt, corev1.EventTypeNormal, "TenantUncordoned", "Tenant has been uncordoned") } diff --git a/pkg/webhook/tenant/hostname_regex.go b/pkg/webhook/tenant/hostname_regex.go index f1184bf3..23c68dc5 100644 --- a/pkg/webhook/tenant/hostname_regex.go +++ b/pkg/webhook/tenant/hostname_regex.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -24,7 +24,7 @@ func HostnameRegexHandler() capsulewebhook.Handler { } func (h *hostnameRegexHandler) validate(decoder *admission.Decoder, req admission.Request) *admission.Response { - tenant := &capsulev1beta1.Tenant{} + tenant := &capsulev1beta2.Tenant{} if err := decoder.Decode(req, tenant); err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/tenant/ingressclass_regex.go b/pkg/webhook/tenant/ingressclass_regex.go index c9e91523..e3349338 100644 --- a/pkg/webhook/tenant/ingressclass_regex.go +++ b/pkg/webhook/tenant/ingressclass_regex.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -24,7 +24,7 @@ func IngressClassRegexHandler() capsulewebhook.Handler { } func (h *ingressClassRegexHandler) validate(decoder *admission.Decoder, req admission.Request) *admission.Response { - tenant := &capsulev1beta1.Tenant{} + tenant := &capsulev1beta2.Tenant{} if err := decoder.Decode(req, tenant); err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/tenant/name.go b/pkg/webhook/tenant/name.go index 0e44a4bb..96ab98b3 100644 --- a/pkg/webhook/tenant/name.go +++ b/pkg/webhook/tenant/name.go @@ -11,7 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -24,7 +24,7 @@ func NameHandler() capsulewebhook.Handler { func (h *nameHandler) OnCreate(_ client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - tenant := &capsulev1beta1.Tenant{} + tenant := &capsulev1beta2.Tenant{} if err := decoder.Decode(req, tenant); err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/tenant/protected.go b/pkg/webhook/tenant/protected.go index b5a5a18d..72d3612f 100644 --- a/pkg/webhook/tenant/protected.go +++ b/pkg/webhook/tenant/protected.go @@ -5,14 +5,13 @@ package tenant import ( "context" - "fmt" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -31,14 +30,14 @@ func (h *protectedHandler) OnCreate(client.Client, *admission.Decoder, record.Ev func (h *protectedHandler) OnDelete(clt client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - tenant := &capsulev1beta1.Tenant{} + tenant := &capsulev1beta2.Tenant{} if err := clt.Get(ctx, types.NamespacedName{Name: req.AdmissionRequest.Name}, tenant); err != nil { return utils.ErroredResponse(err) } - if _, protected := tenant.Annotations[capsulev1beta1.ProtectedTenantAnnotation]; protected { - response := admission.Denied(fmt.Sprintf("tenant is protected and cannot be deleted, remove %s annotation before proceeding", capsulev1beta1.ProtectedTenantAnnotation)) + if tenant.Spec.PreventDeletion { + response := admission.Denied("tenant is protected and cannot be deleted") return &response } diff --git a/pkg/webhook/tenant/rolebindings_regex.go b/pkg/webhook/tenant/rolebindings_regex.go index 63ba86df..cb7a655c 100644 --- a/pkg/webhook/tenant/rolebindings_regex.go +++ b/pkg/webhook/tenant/rolebindings_regex.go @@ -14,7 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -26,7 +26,7 @@ func RoleBindingRegexHandler() capsulewebhook.Handler { } func (h *rbRegexHandler) validate(req admission.Request, decoder *admission.Decoder) *admission.Response { - tenant := &capsulev1beta1.Tenant{} + tenant := &capsulev1beta2.Tenant{} if err := decoder.Decode(req, tenant); err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/tenant/serviceaccount_format.go b/pkg/webhook/tenant/serviceaccount_format.go index 96da0066..6a394b15 100644 --- a/pkg/webhook/tenant/serviceaccount_format.go +++ b/pkg/webhook/tenant/serviceaccount_format.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -24,7 +24,7 @@ func ServiceAccountNameHandler() capsulewebhook.Handler { } func (h *saNameHandler) validateServiceAccountName(req admission.Request, decoder *admission.Decoder) *admission.Response { - tenant := &capsulev1beta1.Tenant{} + tenant := &capsulev1beta2.Tenant{} if err := decoder.Decode(req, tenant); err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/tenant/storageclass_regex.go b/pkg/webhook/tenant/storageclass_regex.go index 93ac9f31..9719ea6a 100644 --- a/pkg/webhook/tenant/storageclass_regex.go +++ b/pkg/webhook/tenant/storageclass_regex.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -24,7 +24,7 @@ func StorageClassRegexHandler() capsulewebhook.Handler { } func (h *storageClassRegexHandler) validate(decoder *admission.Decoder, req admission.Request) *admission.Response { - tenant := &capsulev1beta1.Tenant{} + tenant := &capsulev1beta2.Tenant{} if err := decoder.Decode(req, tenant); err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/utils/is_capsule_user.go b/pkg/webhook/utils/is_capsule_user.go index fd933ea4..aeb37476 100644 --- a/pkg/webhook/utils/is_capsule_user.go +++ b/pkg/webhook/utils/is_capsule_user.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/utils" ) @@ -31,7 +31,7 @@ func IsCapsuleUser(ctx context.Context, req admission.Request, clt client.Client targetNamespace := parts[2] if len(targetNamespace) > 0 { - tl := &capsulev1beta1.TenantList{} + tl := &capsulev1beta2.TenantList{} if err := clt.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", targetNamespace)}); err != nil { return false } diff --git a/pkg/webhook/utils/is_tenant_owner.go b/pkg/webhook/utils/is_tenant_owner.go index 0d6f39b3..bd14f9a4 100644 --- a/pkg/webhook/utils/is_tenant_owner.go +++ b/pkg/webhook/utils/is_tenant_owner.go @@ -6,17 +6,17 @@ package utils import ( authenticationv1 "k8s.io/api/authentication/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) -func IsTenantOwner(owners capsulev1beta1.OwnerListSpec, userInfo authenticationv1.UserInfo) bool { +func IsTenantOwner(owners capsulev1beta2.OwnerListSpec, userInfo authenticationv1.UserInfo) bool { for _, owner := range owners { switch owner.Kind { - case capsulev1beta1.UserOwner, capsulev1beta1.ServiceAccountOwner: + case capsulev1beta2.UserOwner, capsulev1beta2.ServiceAccountOwner: if userInfo.Username == owner.Name { return true } - case capsulev1beta1.GroupOwner: + case capsulev1beta2.GroupOwner: for _, group := range userInfo.Groups { if group == owner.Name { return true From 791dde5bf6a5ea72041489cadb41ca971b5f69dc Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 10:23:57 +0100 Subject: [PATCH 040/153] refactor(e2e): switching to v1beta2 as storage version --- e2e/additional_role_bindings_test.go | 8 +- e2e/allowed_external_ips_test.go | 8 +- e2e/container_registry_test.go | 8 +- e2e/custom_capsule_group_test.go | 16 +- e2e/custom_resource_quota_test.go | 8 +- e2e/disable_externalname_test.go | 8 +- e2e/disable_ingress_wildcard_test.go | 8 +- e2e/disable_loadbalancer_test.go | 8 +- e2e/disable_node_ports_test.go | 8 +- e2e/dynamic_tenant_owner_clusterroles_test.go | 22 ++- e2e/enable_loadbalancer_test.go | 8 +- e2e/enable_node_ports_test.go | 8 +- e2e/forbidden_annotations_regex_test.go | 140 ++++++++++-------- e2e/force_tenant_prefix_test.go | 20 ++- e2e/globaltenantresource_test.go | 13 +- e2e/imagepullpolicy_multiple_test.go | 8 +- e2e/imagepullpolicy_single_test.go | 8 +- e2e/ingress_class_extensions_test.go | 10 +- e2e/ingress_class_networking_test.go | 10 +- ..._hostnames_collision_cluster_scope_test.go | 18 +-- ...gress_hostnames_collision_disabled_test.go | 10 +- ...ostnames_collision_namespace_scope_test.go | 10 +- ...s_hostnames_collision_tenant_scope_test.go | 10 +- e2e/ingress_hostnames_test.go | 10 +- e2e/missing_tenant_test.go | 8 +- e2e/namespace_additional_metadata_test.go | 10 +- e2e/namespace_capsule_label_test.go | 8 +- e2e/namespace_user_metadata_test.go | 25 ++-- e2e/new_namespace_test.go | 8 +- e2e/node_user_metadata_test.go | 53 +++++-- e2e/overquota_namespace_test.go | 10 +- e2e/owner_webhooks_test.go | 8 +- e2e/pod_priority_class_test.go | 8 +- e2e/protected_namespace_regex_test.go | 14 +- e2e/resource_quota_exceeded_test.go | 8 +- e2e/sa_prevent_privilege_escalation_test.go | 8 +- e2e/selecting_non_owned_tenant_test.go | 12 +- e2e/selecting_tenant_fail_test.go | 26 ++-- e2e/selecting_tenant_with_label_test.go | 16 +- e2e/service_metadata_test.go | 8 +- e2e/storage_class_test.go | 8 +- e2e/suite_test.go | 8 +- e2e/tenant_cordoning_test.go | 14 +- e2e/tenant_name_webhook_test.go | 8 +- e2e/tenant_protected_webhook_test.go | 14 +- e2e/tenant_resources_changes_test.go | 8 +- e2e/tenant_resources_test.go | 8 +- e2e/tenantresource_test.go | 7 +- e2e/utils_test.go | 15 +- 49 files changed, 369 insertions(+), 336 deletions(-) diff --git a/e2e/additional_role_bindings_test.go b/e2e/additional_role_bindings_test.go index ffda52fb..e207a189 100644 --- a/e2e/additional_role_bindings_test.go +++ b/e2e/additional_role_bindings_test.go @@ -14,17 +14,17 @@ import ( rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a Namespace with an additional Role Binding", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "additional-role-binding", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "dale", Kind: "User", diff --git a/e2e/allowed_external_ips_test.go b/e2e/allowed_external_ips_test.go index 0842e080..62fdfa1d 100644 --- a/e2e/allowed_external_ips_test.go +++ b/e2e/allowed_external_ips_test.go @@ -14,17 +14,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing an allowed set of Service external IPs", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "allowed-external-ip", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "google", Kind: "User", diff --git a/e2e/container_registry_test.go b/e2e/container_registry_test.go index 92d8ec67..77ff4483 100644 --- a/e2e/container_registry_test.go +++ b/e2e/container_registry_test.go @@ -14,17 +14,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a Container Registry", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "container-registry", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "matt", Kind: "User", diff --git a/e2e/custom_capsule_group_test.go b/e2e/custom_capsule_group_test.go index 531c97d6..d703fefb 100644 --- a/e2e/custom_capsule_group_test.go +++ b/e2e/custom_capsule_group_test.go @@ -12,18 +12,16 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" - - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-group", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-assigned-custom-group", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "alice", Kind: "User", @@ -43,7 +41,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro }) It("should fail using a User non matching the capsule-user-group flag", func() { - ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) { + ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { configuration.Spec.UserGroups = []string{"test"} }) @@ -52,7 +50,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro }) It("should succeed and be available in Tenant namespaces list with multiple groups", func() { - ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) { + ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { configuration.Spec.UserGroups = []string{"test", "alice"} }) @@ -63,7 +61,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro }) It("should succeed and be available in Tenant namespaces list with default single group", func() { - ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) { + ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { configuration.Spec.UserGroups = []string{"capsule.clastix.io"} }) diff --git a/e2e/custom_resource_quota_test.go b/e2e/custom_resource_quota_test.go index 30c0b14c..5354f801 100644 --- a/e2e/custom_resource_quota_test.go +++ b/e2e/custom_resource_quota_test.go @@ -20,19 +20,19 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes/scheme" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("when Tenant limits custom Resource Quota", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "limiting-resources", Annotations: map[string]string{ "quota.resources.capsule.clastix.io/foos.test.clastix.io_v1": "3", }, }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "resource", Kind: "User", diff --git a/e2e/disable_externalname_test.go b/e2e/disable_externalname_test.go index 9d3a1e87..a8b4e6fe 100644 --- a/e2e/disable_externalname_test.go +++ b/e2e/disable_externalname_test.go @@ -15,17 +15,17 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating an ExternalName service when it is disabled for Tenant", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "disable-external-service", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "google", Kind: "User", diff --git a/e2e/disable_ingress_wildcard_test.go b/e2e/disable_ingress_wildcard_test.go index 901a39d7..4c327f17 100644 --- a/e2e/disable_ingress_wildcard_test.go +++ b/e2e/disable_ingress_wildcard_test.go @@ -19,19 +19,19 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating an Ingress with a wildcard when it is denied for the Tenant", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "denied-ingress-wildcard", Annotations: map[string]string{ "capsule.clastix.io/deny-wildcard": "true", }, }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "scott", Kind: "User", diff --git a/e2e/disable_loadbalancer_test.go b/e2e/disable_loadbalancer_test.go index d94983b7..29cfdc35 100644 --- a/e2e/disable_loadbalancer_test.go +++ b/e2e/disable_loadbalancer_test.go @@ -15,17 +15,17 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "disable-loadbalancer-service", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "amazon", Kind: "User", diff --git a/e2e/disable_node_ports_test.go b/e2e/disable_node_ports_test.go index 8e5cb2d9..10f670d6 100644 --- a/e2e/disable_node_ports_test.go +++ b/e2e/disable_node_ports_test.go @@ -15,17 +15,17 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a nodePort service when it is disabled for Tenant", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "disable-node-ports", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "google", Kind: "User", diff --git a/e2e/dynamic_tenant_owner_clusterroles_test.go b/e2e/dynamic_tenant_owner_clusterroles_test.go index b7338875..c491b4b0 100644 --- a/e2e/dynamic_tenant_owner_clusterroles_test.go +++ b/e2e/dynamic_tenant_owner_clusterroles_test.go @@ -13,27 +13,25 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("defining dynamic Tenant Owner Cluster Roles", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "dynamic-tenant-owner-clusterroles", - Annotations: map[string]string{ - "clusterrolenames.capsule.clastix.io/user.michonne": "editor,manager", - "clusterrolenames.capsule.clastix.io/group.kingdom": "readonly", - }, }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { - Name: "michonne", - Kind: "User", + Kind: "User", + Name: "michonne", + ClusterRoles: []string{"editor", "manager"}, }, { - Name: "kingdom", - Kind: "Group", + Name: "kingdom", + Kind: "Group", + ClusterRoles: []string{"readonly"}, }, }, }, diff --git a/e2e/enable_loadbalancer_test.go b/e2e/enable_loadbalancer_test.go index e6201cc0..8990e162 100644 --- a/e2e/enable_loadbalancer_test.go +++ b/e2e/enable_loadbalancer_test.go @@ -15,17 +15,17 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "enable-loadbalancer-service", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "netflix", Kind: "User", diff --git a/e2e/enable_node_ports_test.go b/e2e/enable_node_ports_test.go index b8803f42..1c079439 100644 --- a/e2e/enable_node_ports_test.go +++ b/e2e/enable_node_ports_test.go @@ -14,16 +14,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a nodePort service when it is enabled for Tenant", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "enable-node-ports", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "google", Kind: "User", diff --git a/e2e/forbidden_annotations_regex_test.go b/e2e/forbidden_annotations_regex_test.go index 74e93041..db45297a 100644 --- a/e2e/forbidden_annotations_regex_test.go +++ b/e2e/forbidden_annotations_regex_test.go @@ -12,72 +12,94 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a tenant with various forbidden regexes", func() { - tnt := &capsulev1beta1.Tenant{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: nil, - Name: "namespace", - }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ - { - Name: "alice", - Kind: "User", - }, - }, - }, - } - - It("should succeed when there are no annotations", func() { - EventuallyCreation(func() error { - tnt.ObjectMeta.Annotations = nil - tnt.ResourceVersion = "" - return k8sClient.Create(context.TODO(), tnt) - }).Should(Succeed()) - Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) - }) - - annotationsToCheck := []string{ - capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation, - capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation, - } - - errorRegexes := []string{ - "(.*gitops|.*nsm).[k8s.io/((?!(resource)).*|trusted)](http://k8s.io/((?!(resource)).*%7Ctrusted))", - } - - for _, annotation := range annotationsToCheck { - for _, annotationValue := range errorRegexes { - It("should fail using a non-valid the regex on the annotation "+annotation, func() { - EventuallyCreation(func() error { - tnt.ResourceVersion = "" - tnt.ObjectMeta.Annotations = make(map[string]string) - tnt.ObjectMeta.Annotations[annotation] = annotationValue - return k8sClient.Create(context.TODO(), tnt) - }).ShouldNot(Succeed()) - }) - } - } + //errorRegexes := []string{ + // "(.*gitops|.*nsm).[k8s.io/((?!(resource)).*|trusted)](http://k8s.io/((?!(resource)).*%7Ctrusted))", + //} + // + //for _, annotationValue := range errorRegexes { + // It("should fail using a non-valid the regex on the annotation", func() { + // tnt := &capsulev1beta2.Tenant{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "namespace", + // }, + // Spec: capsulev1beta2.TenantSpec{ + // Owners: capsulev1beta2.OwnerListSpec{ + // { + // Name: "alice", + // Kind: "User", + // }, + // }, + // }, + // } + // + // EventuallyCreation(func() error { + // tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{ + // ForbiddenLabels: api.ForbiddenListSpec{ + // Regex: annotationValue, + // }, + // } + // return k8sClient.Create(context.TODO(), tnt) + // }).ShouldNot(Succeed()) + // + // EventuallyCreation(func() error { + // tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{ + // ForbiddenAnnotations: api.ForbiddenListSpec{ + // Regex: annotationValue, + // }, + // } + // return k8sClient.Create(context.TODO(), tnt) + // }).ShouldNot(Succeed()) + // }) + //} successRegexes := []string{ "", "(.*gitops|.*nsm)", } - for _, annotation := range annotationsToCheck { - for _, annotationValue := range successRegexes { - It("should succeed using a valid regex on the annotation "+annotation, func() { - EventuallyCreation(func() error { - tnt.ResourceVersion = "" - tnt.ObjectMeta.Annotations = make(map[string]string) - tnt.ObjectMeta.Annotations[annotation] = annotationValue - return k8sClient.Create(context.TODO(), tnt) - }).Should(Succeed()) - Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) - }) - } - } + for _, annotationValue := range successRegexes { + It("should succeed using a valid regex on the annotation", func() { + tnt := &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ + { + Name: "alice", + Kind: "User", + }, + }, + }, + } + EventuallyCreation(func() error { + tnt.SetResourceVersion("") + + tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{ + ForbiddenLabels: api.ForbiddenListSpec{ + Regex: annotationValue, + }, + } + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + + EventuallyCreation(func() error { + tnt.SetResourceVersion("") + + tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{ + ForbiddenAnnotations: api.ForbiddenListSpec{ + Regex: annotationValue, + }, + } + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + }) + } }) diff --git a/e2e/force_tenant_prefix_test.go b/e2e/force_tenant_prefix_test.go index 6ac706e9..488c8682 100644 --- a/e2e/force_tenant_prefix_test.go +++ b/e2e/force_tenant_prefix_test.go @@ -12,18 +12,16 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" - - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a Namespace with Tenant name prefix enforcement", func() { - t1 := &capsulev1beta1.Tenant{ + t1 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "awesome", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "User", @@ -31,12 +29,12 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", fun }, }, } - t2 := &capsulev1beta1.Tenant{ + t2 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "awesome-tenant", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "User", @@ -55,7 +53,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", fun return k8sClient.Create(context.TODO(), t2) }).Should(Succeed()) - ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) { + ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { configuration.Spec.ForceTenantPrefix = true }) }) @@ -63,7 +61,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", fun Expect(k8sClient.Delete(context.TODO(), t1)).Should(Succeed()) Expect(k8sClient.Delete(context.TODO(), t2)).Should(Succeed()) - ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) { + ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { configuration.Spec.ForceTenantPrefix = false }) }) diff --git a/e2e/globaltenantresource_test.go b/e2e/globaltenantresource_test.go index 0f2c9144..b0ebf1fe 100644 --- a/e2e/globaltenantresource_test.go +++ b/e2e/globaltenantresource_test.go @@ -21,21 +21,20 @@ import ( "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("Creating a GlobalTenantResource object", func() { - solar := &capsulev1beta1.Tenant{ + solar := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "energy-solar", Labels: map[string]string{ "replicate": "true", }, }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "solar-user", Kind: "User", @@ -44,15 +43,15 @@ var _ = Describe("Creating a GlobalTenantResource object", func() { }, } - wind := &capsulev1beta1.Tenant{ + wind := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "energy-wind", Labels: map[string]string{ "replicate": "true", }, }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "wind-user", Kind: "User", diff --git a/e2e/imagepullpolicy_multiple_test.go b/e2e/imagepullpolicy_multiple_test.go index d725c4f6..78beaac9 100644 --- a/e2e/imagepullpolicy_multiple_test.go +++ b/e2e/imagepullpolicy_multiple_test.go @@ -13,17 +13,17 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing some defined ImagePullPolicy", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "image-pull-policies", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "alex", Kind: "User", diff --git a/e2e/imagepullpolicy_single_test.go b/e2e/imagepullpolicy_single_test.go index 9b99e349..1d3f873b 100644 --- a/e2e/imagepullpolicy_single_test.go +++ b/e2e/imagepullpolicy_single_test.go @@ -13,17 +13,17 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a defined ImagePullPolicy", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "image-pull-policy", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "axel", Kind: "User", diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index deb949c2..0ffaa691 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -18,23 +18,23 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "ingress-class-extensions-v1beta1", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "ingress", Kind: "User", }, }, - IngressOptions: capsulev1beta1.IngressOptions{ + IngressOptions: capsulev1beta2.IngressOptions{ AllowedClasses: &api.AllowedListSpec{ Exact: []string{ "nginx", diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index 14a70227..500a1cbf 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -17,23 +17,23 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "ingress-class-networking-v1", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: []capsulev1beta1.OwnerSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: []capsulev1beta2.OwnerSpec{ { Name: "ingress", Kind: "User", }, }, - IngressOptions: capsulev1beta1.IngressOptions{ + IngressOptions: capsulev1beta2.IngressOptions{ AllowedClasses: &api.AllowedListSpec{ Exact: []string{ "nginx", diff --git a/e2e/ingress_hostnames_collision_cluster_scope_test.go b/e2e/ingress_hostnames_collision_cluster_scope_test.go index de503367..fa23e49f 100644 --- a/e2e/ingress_hostnames_collision_cluster_scope_test.go +++ b/e2e/ingress_hostnames_collision_cluster_scope_test.go @@ -18,39 +18,39 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when handling Cluster scoped Ingress hostnames collision", func() { - tnt1 := &capsulev1beta1.Tenant{ + tnt1 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "hostnames-collision-cluster-one", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "ingress-tenant-one", Kind: "User", }, }, - IngressOptions: capsulev1beta1.IngressOptions{ + IngressOptions: capsulev1beta2.IngressOptions{ HostnameCollisionScope: api.HostnameCollisionScopeCluster, }, }, } - tnt2 := &capsulev1beta1.Tenant{ + tnt2 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "hostnames-collision-cluster-two", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "ingress-tenant-two", Kind: "User", }, }, - IngressOptions: capsulev1beta1.IngressOptions{ + IngressOptions: capsulev1beta2.IngressOptions{ HostnameCollisionScope: api.HostnameCollisionScopeCluster, }, }, diff --git a/e2e/ingress_hostnames_collision_disabled_test.go b/e2e/ingress_hostnames_collision_disabled_test.go index 081dfc9c..e8139ec7 100644 --- a/e2e/ingress_hostnames_collision_disabled_test.go +++ b/e2e/ingress_hostnames_collision_disabled_test.go @@ -18,23 +18,23 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when disabling Ingress hostnames collision", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "hostnames-collision-disabled", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "ingress-disabled", Kind: "User", }, }, - IngressOptions: capsulev1beta1.IngressOptions{ + IngressOptions: capsulev1beta2.IngressOptions{ HostnameCollisionScope: api.HostnameCollisionScopeDisabled, }, }, diff --git a/e2e/ingress_hostnames_collision_namespace_scope_test.go b/e2e/ingress_hostnames_collision_namespace_scope_test.go index bfc43b95..66b5eabf 100644 --- a/e2e/ingress_hostnames_collision_namespace_scope_test.go +++ b/e2e/ingress_hostnames_collision_namespace_scope_test.go @@ -18,23 +18,23 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when handling Namespace scoped Ingress hostnames collision", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "hostnames-collision-namespace", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "ingress-namespace", Kind: "User", }, }, - IngressOptions: capsulev1beta1.IngressOptions{ + IngressOptions: capsulev1beta2.IngressOptions{ HostnameCollisionScope: api.HostnameCollisionScopeNamespace, }, }, diff --git a/e2e/ingress_hostnames_collision_tenant_scope_test.go b/e2e/ingress_hostnames_collision_tenant_scope_test.go index 6e3b67b9..7081ae02 100644 --- a/e2e/ingress_hostnames_collision_tenant_scope_test.go +++ b/e2e/ingress_hostnames_collision_tenant_scope_test.go @@ -18,23 +18,23 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "hostnames-collision-tenant", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "ingress-tenant", Kind: "User", }, }, - IngressOptions: capsulev1beta1.IngressOptions{ + IngressOptions: capsulev1beta2.IngressOptions{ HostnameCollisionScope: api.HostnameCollisionScopeTenant, }, }, diff --git a/e2e/ingress_hostnames_test.go b/e2e/ingress_hostnames_test.go index 0c12fd80..cabf671e 100644 --- a/e2e/ingress_hostnames_test.go +++ b/e2e/ingress_hostnames_test.go @@ -18,23 +18,23 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Ingress hostnames", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "ingress-hostnames", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "hostname", Kind: "User", }, }, - IngressOptions: capsulev1beta1.IngressOptions{ + IngressOptions: capsulev1beta2.IngressOptions{ AllowedHostnames: &api.AllowedListSpec{ Exact: []string{"sigs.k8s.io", "operator.sdk", "domain.tld"}, Regex: `.*\.clastix\.io`, diff --git a/e2e/missing_tenant_test.go b/e2e/missing_tenant_test.go index 1ff51b66..5202bb9b 100644 --- a/e2e/missing_tenant_test.go +++ b/e2e/missing_tenant_test.go @@ -12,14 +12,14 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a Namespace creation with no Tenant assigned", func() { It("should fail", func() { - tnt := &capsulev1beta1.Tenant{ - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + tnt := &capsulev1beta2.Tenant{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "missing", Kind: "User", diff --git a/e2e/namespace_additional_metadata_test.go b/e2e/namespace_additional_metadata_test.go index 68452022..87e00c19 100644 --- a/e2e/namespace_additional_metadata_test.go +++ b/e2e/namespace_additional_metadata_test.go @@ -13,23 +13,23 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a Namespace for a Tenant with additional metadata", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-metadata", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "gatsby", Kind: "User", }, }, - NamespaceOptions: &capsulev1beta1.NamespaceOptions{ + NamespaceOptions: &capsulev1beta2.NamespaceOptions{ AdditionalMetadata: &api.AdditionalMetadataSpec{ Labels: map[string]string{ "k8s.io/custom-label": "foo", diff --git a/e2e/namespace_capsule_label_test.go b/e2e/namespace_capsule_label_test.go index bb223550..21ac4582 100644 --- a/e2e/namespace_capsule_label_test.go +++ b/e2e/namespace_capsule_label_test.go @@ -14,16 +14,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating several Namespaces for a Tenant", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "capsule-labels", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "charlie", Kind: "User", diff --git a/e2e/namespace_user_metadata_test.go b/e2e/namespace_user_metadata_test.go index 298b5a18..b59a77c2 100644 --- a/e2e/namespace_user_metadata_test.go +++ b/e2e/namespace_user_metadata_test.go @@ -15,22 +15,27 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("creating a Namespace with user-specified labels and annotations", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-user-metadata-forbidden", - Annotations: map[string]string{ - capsulev1beta1.ForbiddenNamespaceLabelsAnnotation: "foo,bar", - capsulev1beta1.ForbiddenNamespaceLabelsRegexpAnnotation: "^gatsby-.*$", - capsulev1beta1.ForbiddenNamespaceAnnotationsAnnotation: "foo,bar", - capsulev1beta1.ForbiddenNamespaceAnnotationsRegexpAnnotation: "^gatsby-.*$", - }, }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + NamespaceOptions: &capsulev1beta2.NamespaceOptions{ + ForbiddenLabels: api.ForbiddenListSpec{ + Exact: []string{"foo", "bar"}, + Regex: "^gatsby-.*$", + }, + ForbiddenAnnotations: api.ForbiddenListSpec{ + Exact: []string{"foo", "bar"}, + Regex: "^gatsby-.*$", + }, + }, + Owners: capsulev1beta2.OwnerListSpec{ { Name: "gatsby", Kind: "User", diff --git a/e2e/new_namespace_test.go b/e2e/new_namespace_test.go index a14e6c04..6d6cfc20 100644 --- a/e2e/new_namespace_test.go +++ b/e2e/new_namespace_test.go @@ -12,16 +12,16 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a Namespaces as different type of Tenant owners", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-assigned", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "alice", Kind: "User", diff --git a/e2e/node_user_metadata_test.go b/e2e/node_user_metadata_test.go index f898147a..1ded6b50 100644 --- a/e2e/node_user_metadata_test.go +++ b/e2e/node_user_metadata_test.go @@ -7,25 +7,26 @@ package e2e import ( "context" - "fmt" - capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" - "github.com/clastix/capsule/pkg/webhook/utils" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/webhook/utils" ) var _ = Describe("modifying node labels and annotations", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-node-user-metadata-forbidden", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "gatsby", Kind: "User", @@ -90,17 +91,41 @@ var _ = Describe("modifying node labels and annotations", func() { Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) Expect(k8sClient.Delete(context.TODO(), crb)).Should(Succeed()) Expect(k8sClient.Delete(context.TODO(), cr)).Should(Succeed()) + EventuallyCreation(func() error { + return ModifyNode(func(node *corev1.Node) error { + annotations := node.GetAnnotations() + + delete(annotations, "bim") + delete(annotations, "foo") + delete(annotations, "gatsby-foo") + + node.SetAnnotations(annotations) + + labels := node.GetLabels() + + delete(labels, "bim") + delete(labels, "foo") + delete(labels, "gatsby-foo") + + node.SetLabels(labels) + + return k8sClient.Update(context.Background(), node) + }) + }).Should(Succeed()) }) It("should allow", func() { - ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) { - protected := map[string]string{ - capsulev1alpha1.ForbiddenNodeLabelsAnnotation: "foo,bar", - capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation: "^gatsby-.*$", - capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation: "foo,bar", - capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation: "^gatsby-.*$", + ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { + configuration.Spec.NodeMetadata = &capsulev1beta2.NodeMetadata{ + ForbiddenLabels: api.ForbiddenListSpec{ + Exact: []string{"foo", "bar"}, + Regex: "^gatsby-.*$", + }, + ForbiddenAnnotations: api.ForbiddenListSpec{ + Exact: []string{"foo", "bar"}, + Regex: "^gatsby-.*$", + }, } - configuration.SetAnnotations(protected) }) By("adding non-forbidden labels", func() { EventuallyCreation(func() error { diff --git a/e2e/overquota_namespace_test.go b/e2e/overquota_namespace_test.go index 986c55c5..ab064b61 100644 --- a/e2e/overquota_namespace_test.go +++ b/e2e/overquota_namespace_test.go @@ -13,22 +13,22 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a Namespace in over-quota of three", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "over-quota-tenant", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "bob", Kind: "User", }, }, - NamespaceOptions: &capsulev1beta1.NamespaceOptions{ + NamespaceOptions: &capsulev1beta2.NamespaceOptions{ Quota: pointer.Int32Ptr(3), }, }, diff --git a/e2e/owner_webhooks_test.go b/e2e/owner_webhooks_test.go index 63a8fecc..55e90e9d 100644 --- a/e2e/owner_webhooks_test.go +++ b/e2e/owner_webhooks_test.go @@ -17,17 +17,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant owner interacts with the webhooks", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-owner", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "ruby", Kind: "User", diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index 852ec00b..93300ee0 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -14,17 +14,17 @@ import ( v1 "k8s.io/api/scheduling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a Priority Class", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "priority-class", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "george", Kind: "User", diff --git a/e2e/protected_namespace_regex_test.go b/e2e/protected_namespace_regex_test.go index 7dadb2a4..1b878cf1 100644 --- a/e2e/protected_namespace_regex_test.go +++ b/e2e/protected_namespace_regex_test.go @@ -12,18 +12,16 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" - - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a Namespace with a protected Namespace regex enabled", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-protected-namespace", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "alice", Kind: "User", @@ -43,7 +41,7 @@ var _ = Describe("creating a Namespace with a protected Namespace regex enabled" }) It("should succeed and be available in Tenant namespaces list", func() { - ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) { + ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { configuration.Spec.ProtectedNamespaceRegexpString = `^.*[-.]system$` }) @@ -57,7 +55,7 @@ var _ = Describe("creating a Namespace with a protected Namespace regex enabled" ns := NewNamespace("test-system") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) - ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) { + ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { configuration.Spec.ProtectedNamespaceRegexpString = "" }) }) diff --git a/e2e/resource_quota_exceeded_test.go b/e2e/resource_quota_exceeded_test.go index d8edddbd..18e359f8 100644 --- a/e2e/resource_quota_exceeded_test.go +++ b/e2e/resource_quota_exceeded_test.go @@ -19,16 +19,16 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/utils/pointer" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("exceeding a Tenant resource quota", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-resources-changes", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "bobby", Kind: "User", diff --git a/e2e/sa_prevent_privilege_escalation_test.go b/e2e/sa_prevent_privilege_escalation_test.go index 59649e58..f2bde523 100644 --- a/e2e/sa_prevent_privilege_escalation_test.go +++ b/e2e/sa_prevent_privilege_escalation_test.go @@ -19,16 +19,16 @@ import ( "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client/config" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("trying to escalate from a Tenant Namespace ServiceAccount", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "sa-privilege-escalation", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "mario", Kind: "User", diff --git a/e2e/selecting_non_owned_tenant_test.go b/e2e/selecting_non_owned_tenant_test.go index 82ec38f3..3709db27 100644 --- a/e2e/selecting_non_owned_tenant_test.go +++ b/e2e/selecting_non_owned_tenant_test.go @@ -14,16 +14,16 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a Namespace trying to select a third Tenant", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-non-owned", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "undefined", Kind: "User", @@ -45,7 +45,7 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", func() var ns *corev1.Namespace By("assigning to the Namespace the Capsule Tenant label", func() { - l, err := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) + l, err := utils.GetTypeLabel(&capsulev1beta2.Tenant{}) Expect(err).ToNot(HaveOccurred()) ns := NewNamespace("") @@ -54,7 +54,7 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", func() }) }) - cs := ownerClient(capsulev1beta1.OwnerSpec{Name: "dale", Kind: "User"}) + cs := ownerClient(capsulev1beta2.OwnerSpec{Name: "dale", Kind: "User"}) _, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) Expect(err).To(HaveOccurred()) }) diff --git a/e2e/selecting_tenant_fail_test.go b/e2e/selecting_tenant_fail_test.go index 960a98ec..6d4242b7 100644 --- a/e2e/selecting_tenant_fail_test.go +++ b/e2e/selecting_tenant_fail_test.go @@ -12,16 +12,16 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a Namespace without a Tenant selector when user owns multiple Tenants", func() { - t1 := &capsulev1beta1.Tenant{ + t1 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-one", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "User", @@ -29,12 +29,12 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns }, }, } - t2 := &capsulev1beta1.Tenant{ + t2 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-two", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "User", @@ -42,12 +42,12 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns }, }, } - t3 := &capsulev1beta1.Tenant{ + t3 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-three", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "Group", @@ -55,12 +55,12 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns }, }, } - t4 := &capsulev1beta1.Tenant{ + t4 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-four", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "Group", diff --git a/e2e/selecting_tenant_with_label_test.go b/e2e/selecting_tenant_with_label_test.go index 9f1270c7..9398852b 100644 --- a/e2e/selecting_tenant_with_label_test.go +++ b/e2e/selecting_tenant_with_label_test.go @@ -13,16 +13,16 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a Namespace with Tenant selector when user owns multiple tenants", func() { - t1 := &capsulev1beta1.Tenant{ + t1 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-one", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "User", @@ -30,12 +30,12 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi }, }, } - t2 := &capsulev1beta1.Tenant{ + t2 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-two", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "User", @@ -60,7 +60,7 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi It("should be assigned to the selected Tenant", func() { ns := NewNamespace("") By("assigning to the Namespace the Capsule Tenant label", func() { - l, err := utils.GetTypeLabel(&capsulev1beta1.Tenant{}) + l, err := utils.GetTypeLabel(&capsulev1beta2.Tenant{}) Expect(err).ToNot(HaveOccurred()) ns.Labels = map[string]string{ l: t2.Name, diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index 3f86772b..f9611dae 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -23,17 +23,17 @@ import ( "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("adding metadata to Service objects", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "service-metadata", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "gatsby", Kind: "User", diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go index e5a1c273..7cd27dcc 100644 --- a/e2e/storage_class_test.go +++ b/e2e/storage_class_test.go @@ -15,17 +15,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Storage classes", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "storage-class", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "storage", Kind: "User", diff --git a/e2e/suite_test.go b/e2e/suite_test.go index 4748f5b3..0f890d18 100644 --- a/e2e/suite_test.go +++ b/e2e/suite_test.go @@ -20,8 +20,6 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) @@ -58,8 +56,6 @@ var _ = BeforeSuite(func(done Done) { Expect(err).ToNot(HaveOccurred()) Expect(cfg).ToNot(BeNil()) - Expect(capsulev1alpha1.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) - Expect(capsulev1beta1.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) Expect(capsulev1beta2.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) @@ -74,10 +70,10 @@ var _ = AfterSuite(func() { Expect(testEnv.Stop()).ToNot(HaveOccurred()) }) -func ownerClient(owner capsulev1beta1.OwnerSpec) (cs kubernetes.Interface) { +func ownerClient(owner capsulev1beta2.OwnerSpec) (cs kubernetes.Interface) { c, err := config.GetConfig() Expect(err).ToNot(HaveOccurred()) - c.Impersonate.Groups = []string{capsulev1beta1.GroupVersion.Group, owner.Name} + c.Impersonate.Groups = []string{capsulev1beta2.GroupVersion.Group, owner.Name} c.Impersonate.UserName = owner.Name cs, err = kubernetes.NewForConfig(c) Expect(err).ToNot(HaveOccurred()) diff --git a/e2e/tenant_cordoning_test.go b/e2e/tenant_cordoning_test.go index 4ece4159..069275fb 100644 --- a/e2e/tenant_cordoning_test.go +++ b/e2e/tenant_cordoning_test.go @@ -15,16 +15,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("cordoning a Tenant", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-cordoning", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "jim", Kind: "User", @@ -75,9 +75,7 @@ var _ = Describe("cordoning a Tenant", func() { By("cordoning the Tenant deletion must be blocked", func() { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.Name}, tnt)).Should(Succeed()) - tnt.Labels = map[string]string{ - "capsule.clastix.io/cordon": "enabled", - } + tnt.Spec.Cordoned = true Expect(k8sClient.Update(context.TODO(), tnt)).Should(Succeed()) @@ -89,7 +87,7 @@ var _ = Describe("cordoning a Tenant", func() { By("uncordoning the Tenant deletion must be allowed", func() { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.Name}, tnt)).Should(Succeed()) - tnt.Labels = map[string]string{} + tnt.Spec.Cordoned = false Expect(k8sClient.Update(context.TODO(), tnt)).Should(Succeed()) diff --git a/e2e/tenant_name_webhook_test.go b/e2e/tenant_name_webhook_test.go index d6d2ae80..d555bf29 100644 --- a/e2e/tenant_name_webhook_test.go +++ b/e2e/tenant_name_webhook_test.go @@ -12,16 +12,16 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating a Tenant with wrong name", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "non_rfc_dns_1123", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "User", diff --git a/e2e/tenant_protected_webhook_test.go b/e2e/tenant_protected_webhook_test.go index 108f35e5..fcf78842 100644 --- a/e2e/tenant_protected_webhook_test.go +++ b/e2e/tenant_protected_webhook_test.go @@ -13,19 +13,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("Deleting a tenant with protected annotation", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "protected-tenant", - Annotations: map[string]string{ - capsulev1beta1.ProtectedTenantAnnotation: "", - }, }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + PreventDeletion: true, + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "User", @@ -36,7 +34,7 @@ var _ = Describe("Deleting a tenant with protected annotation", func() { JustAfterEach(func() { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, tnt)).Should(Succeed()) - tnt.SetAnnotations(map[string]string{}) + tnt.Spec.PreventDeletion = false Expect(k8sClient.Update(context.TODO(), tnt)).Should(Succeed()) Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) }) diff --git a/e2e/tenant_resources_changes_test.go b/e2e/tenant_resources_changes_test.go index 5cd2d0dd..4eb08757 100644 --- a/e2e/tenant_resources_changes_test.go +++ b/e2e/tenant_resources_changes_test.go @@ -19,16 +19,16 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("changing Tenant managed Kubernetes resources", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-resources-changes", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "laura", Kind: "User", diff --git a/e2e/tenant_resources_test.go b/e2e/tenant_resources_test.go index 80742b8d..c8f6805d 100644 --- a/e2e/tenant_resources_test.go +++ b/e2e/tenant_resources_test.go @@ -19,16 +19,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) var _ = Describe("creating namespaces within a Tenant with resources", func() { - tnt := &capsulev1beta1.Tenant{ + tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-resources", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "john", Kind: "User", diff --git a/e2e/tenantresource_test.go b/e2e/tenantresource_test.go index e1cb8c1a..a1d7cede 100644 --- a/e2e/tenantresource_test.go +++ b/e2e/tenantresource_test.go @@ -22,18 +22,17 @@ import ( "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("Creating a TenantResource object", func() { - solar := &capsulev1beta1.Tenant{ + solar := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "energy-solar", }, - Spec: capsulev1beta1.TenantSpec{ - Owners: capsulev1beta1.OwnerListSpec{ + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ { Name: "solar-user", Kind: "User", diff --git a/e2e/utils_test.go b/e2e/utils_test.go index ad0cc1ba..46ec50d1 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -23,8 +23,7 @@ import ( "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/kubernetes" - capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" - capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) const ( @@ -44,7 +43,7 @@ func NewNamespace(name string) *corev1.Namespace { } } -func NamespaceCreation(ns *corev1.Namespace, owner capsulev1beta1.OwnerSpec, timeout time.Duration) AsyncAssertion { +func NamespaceCreation(ns *corev1.Namespace, owner capsulev1beta2.OwnerSpec, timeout time.Duration) AsyncAssertion { cs := ownerClient(owner) return Eventually(func() (err error) { _, err = cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) @@ -52,7 +51,7 @@ func NamespaceCreation(ns *corev1.Namespace, owner capsulev1beta1.OwnerSpec, tim }, timeout, defaultPollInterval) } -func TenantNamespaceList(t *capsulev1beta1.Tenant, timeout time.Duration) AsyncAssertion { +func TenantNamespaceList(t *capsulev1beta2.Tenant, timeout time.Duration) AsyncAssertion { return Eventually(func() []string { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: t.GetName()}, t)).Should(Succeed()) return t.Status.Namespaces @@ -71,8 +70,8 @@ func EventuallyCreation(f interface{}) AsyncAssertion { return Eventually(f, defaultTimeoutInterval, defaultPollInterval) } -func ModifyCapsuleConfigurationOpts(fn func(configuration *capsulev1alpha1.CapsuleConfiguration)) { - config := &capsulev1alpha1.CapsuleConfiguration{} +func ModifyCapsuleConfigurationOpts(fn func(configuration *capsulev1beta2.CapsuleConfiguration)) { + config := &capsulev1beta2.CapsuleConfiguration{} Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "default"}, config)).ToNot(HaveOccurred()) fn(config) @@ -82,7 +81,7 @@ func ModifyCapsuleConfigurationOpts(fn func(configuration *capsulev1alpha1.Capsu time.Sleep(1 * time.Second) } -func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner capsulev1beta1.OwnerSpec, roles map[string]bool) func() error { +func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner capsulev1beta2.OwnerSpec, roles map[string]bool) func() error { if roles == nil { roles = map[string]bool{ "admin": false, @@ -99,7 +98,7 @@ func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner capsulev1beta1.OwnerS var ownerName string - if owner.Kind == capsulev1beta1.ServiceAccountOwner { + if owner.Kind == capsulev1beta2.ServiceAccountOwner { parts := strings.Split(owner.Name, ":") ownerName = parts[3] From 77a8c9ab62f939118f58be8c5086317d6f1139db Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 10:28:59 +0100 Subject: [PATCH 041/153] chore(kustomize): switching to v1beta2 as storage version --- .../bases/capsule.clastix.io_capsuleconfigurations.yaml | 6 ++++-- config/install.yaml | 8 +++++--- config/webhook/manifests.yaml | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml index 460208a9..f2b41739 100644 --- a/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml +++ b/config/crd/bases/capsule.clastix.io_capsuleconfigurations.yaml @@ -126,6 +126,10 @@ spec: - forbiddenLabels type: object overrides: + default: + TLSSecretName: capsule-tls + mutatingWebhookConfigurationName: capsule-mutating-webhook-configuration + validatingWebhookConfigurationName: capsule-validating-webhook-configuration description: Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations. @@ -164,8 +168,6 @@ spec: type: array required: - enableTLSReconciler - - nodeMetadata - - overrides type: object type: object served: true diff --git a/config/install.yaml b/config/install.yaml index 0c04ecc9..5a0e2ff8 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -118,6 +118,10 @@ spec: - forbiddenLabels type: object overrides: + default: + TLSSecretName: capsule-tls + mutatingWebhookConfigurationName: capsule-mutating-webhook-configuration + validatingWebhookConfigurationName: capsule-validating-webhook-configuration description: Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations. properties: TLSSecretName: @@ -149,8 +153,6 @@ spec: type: array required: - enableTLSReconciler - - nodeMetadata - - overrides type: object type: object served: true @@ -2926,7 +2928,7 @@ webhooks: - apiGroups: - capsule.clastix.io apiVersions: - - v1beta1 + - v1beta2 operations: - CREATE - UPDATE diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 768a76f0..4567309d 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -206,7 +206,7 @@ webhooks: - apiGroups: - capsule.clastix.io apiVersions: - - v1beta1 + - v1beta2 operations: - CREATE - UPDATE From 5af3c9828e11378e0c4c20d0ed6bf385d7e09907 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 10:29:43 +0100 Subject: [PATCH 042/153] chore(helm): switching to v1beta2 as storage version --- charts/capsule/crds/capsuleconfiguration-crd.yaml | 6 ++++-- charts/capsule/templates/configuration-default.yaml | 11 ++++++----- .../templates/validatingwebhookconfiguration.yaml | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/charts/capsule/crds/capsuleconfiguration-crd.yaml b/charts/capsule/crds/capsuleconfiguration-crd.yaml index a2a42efa..78c7945e 100644 --- a/charts/capsule/crds/capsuleconfiguration-crd.yaml +++ b/charts/capsule/crds/capsuleconfiguration-crd.yaml @@ -113,6 +113,10 @@ spec: - forbiddenLabels type: object overrides: + default: + TLSSecretName: capsule-tls + mutatingWebhookConfigurationName: capsule-mutating-webhook-configuration + validatingWebhookConfigurationName: capsule-validating-webhook-configuration description: Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations. properties: TLSSecretName: @@ -144,8 +148,6 @@ spec: type: array required: - enableTLSReconciler - - nodeMetadata - - overrides type: object type: object served: true diff --git a/charts/capsule/templates/configuration-default.yaml b/charts/capsule/templates/configuration-default.yaml index 3cb897a1..1356fd58 100644 --- a/charts/capsule/templates/configuration-default.yaml +++ b/charts/capsule/templates/configuration-default.yaml @@ -1,18 +1,19 @@ -apiVersion: capsule.clastix.io/v1alpha1 +apiVersion: capsule.clastix.io/v1beta2 kind: CapsuleConfiguration metadata: name: default labels: {{- include "capsule.labels" . | nindent 4 }} annotations: - capsule.clastix.io/mutating-webhook-configuration-name: {{ include "capsule.fullname" . }}-mutating-webhook-configuration - capsule.clastix.io/tls-secret-name: {{ include "capsule.secretTlsName" . }} - capsule.clastix.io/validating-webhook-configuration-name: {{ include "capsule.fullname" . }}-validating-webhook-configuration - capsule.clastix.io/enable-tls-configuration: "{{ .Values.tls.enableController }}" {{- with .Values.customAnnotations }} {{- toYaml . | nindent 4 }} {{- end }} spec: + enableTLSReconciler: {{ .Values.tls.enableController }} + overrides: + mutatingWebhookConfigurationName: {{ include "capsule.fullname" . }}-mutating-webhook-configuration + TLSSecretName: {{ include "capsule.secretTlsName" . }} + validatingWebhookConfigurationName: {{ include "capsule.fullname" . }}-validating-webhook-configuration forceTenantPrefix: {{ .Values.manager.options.forceTenantPrefix }} userGroups: {{- range .Values.manager.options.capsuleUserGroups }} diff --git a/charts/capsule/templates/validatingwebhookconfiguration.yaml b/charts/capsule/templates/validatingwebhookconfiguration.yaml index e20ae09c..01e2b327 100644 --- a/charts/capsule/templates/validatingwebhookconfiguration.yaml +++ b/charts/capsule/templates/validatingwebhookconfiguration.yaml @@ -249,7 +249,7 @@ webhooks: - apiGroups: - capsule.clastix.io apiVersions: - - v1beta1 + - v1beta2 operations: - CREATE - UPDATE From 289b0795309f1e4789ee84e2d71453a7340edf4a Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 10:29:50 +0100 Subject: [PATCH 043/153] docs(api): aligning to latest changes for capsule configuration --- docs/content/general/crds-apis.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/content/general/crds-apis.md b/docs/content/general/crds-apis.md index b6b091dd..19ac9b7f 100644 --- a/docs/content/general/crds-apis.md +++ b/docs/content/general/crds-apis.md @@ -1665,27 +1665,29 @@ CapsuleConfigurationSpec defines the Capsule configuration. Default: true
+ + + + + - + - - - - - From 93fbca9b1821d281a042620861aa83a57f561af8 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 15:31:59 +0100 Subject: [PATCH 044/153] feat(api): label selector for storage, ingress, podpriority classes --- api/v1alpha1/conversion_hub_test.go | 16 +++--- api/v1beta2/ingress_options.go | 2 +- api/v1beta2/tenant_conversion_hub.go | 28 ++++++++-- api/v1beta2/tenant_types.go | 4 +- api/v1beta2/zz_generated.deepcopy.go | 6 +- pkg/api/allowed_list.go | 21 +++++++ pkg/api/zz_generated.deepcopy.go | 17 ++++++ pkg/webhook/ingress/errors.go | 14 +++-- pkg/webhook/ingress/validate_class.go | 73 ++++++++++++++++++++++--- pkg/webhook/pod/priorityclass.go | 31 ++++++++++- pkg/webhook/pod/priorityclass_errors.go | 8 ++- pkg/webhook/pvc/errors.go | 14 +++-- pkg/webhook/pvc/validating.go | 40 +++++++++++++- 13 files changed, 232 insertions(+), 42 deletions(-) diff --git a/api/v1alpha1/conversion_hub_test.go b/api/v1alpha1/conversion_hub_test.go index 275bfc1b..f2d4e9ab 100644 --- a/api/v1alpha1/conversion_hub_test.go +++ b/api/v1alpha1/conversion_hub_test.go @@ -61,9 +61,11 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { Allowed: []api.AllowedIP{"192.168.0.1"}, }, } - v1beta1AllowedListSpec := &api.AllowedListSpec{ - Exact: []string{"foo", "bar"}, - Regex: "^foo*", + v1beta2AllowedListSpec := &api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{"foo", "bar"}, + Regex: "^foo*", + }, } networkPolicies := []networkingv1.NetworkPolicySpec{ { @@ -235,13 +237,13 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { }, NamespaceOptions: v1beta1NamespaceOptions, ServiceOptions: v1beta1ServiceOptions, - StorageClasses: v1beta1AllowedListSpec, + StorageClasses: &v1beta2AllowedListSpec.AllowedListSpec, IngressOptions: capsulev1beta1.IngressOptions{ HostnameCollisionScope: api.HostnameCollisionScopeDisabled, - AllowedClasses: v1beta1AllowedListSpec, - AllowedHostnames: v1beta1AllowedListSpec, + AllowedClasses: &v1beta2AllowedListSpec.AllowedListSpec, + AllowedHostnames: &v1beta2AllowedListSpec.AllowedListSpec, }, - ContainerRegistries: v1beta1AllowedListSpec, + ContainerRegistries: &v1beta2AllowedListSpec.AllowedListSpec, NodeSelector: nodeSelector, NetworkPolicies: api.NetworkPolicySpec{ Items: networkPolicies, diff --git a/api/v1beta2/ingress_options.go b/api/v1beta2/ingress_options.go index 3d4cd718..9e821afc 100644 --- a/api/v1beta2/ingress_options.go +++ b/api/v1beta2/ingress_options.go @@ -9,7 +9,7 @@ import ( type IngressOptions struct { // Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. - AllowedClasses *api.AllowedListSpec `json:"allowedClasses,omitempty"` + AllowedClasses *api.SelectorAllowedListSpec `json:"allowedClasses,omitempty"` // Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. // // diff --git a/api/v1beta2/tenant_conversion_hub.go b/api/v1beta2/tenant_conversion_hub.go index 4256e252..a3dee20a 100644 --- a/api/v1beta2/tenant_conversion_hub.go +++ b/api/v1beta2/tenant_conversion_hub.go @@ -85,7 +85,11 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { } in.Spec.ServiceOptions = src.Spec.ServiceOptions - in.Spec.StorageClasses = src.Spec.StorageClasses + if src.Spec.StorageClasses != nil { + in.Spec.StorageClasses = &api.SelectorAllowedListSpec{ + AllowedListSpec: *src.Spec.StorageClasses, + } + } if scope := src.Spec.IngressOptions.HostnameCollisionScope; len(scope) > 0 { in.Spec.IngressOptions.HostnameCollisionScope = scope @@ -102,7 +106,9 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { } if ingressClass := src.Spec.IngressOptions.AllowedClasses; ingressClass != nil { - in.Spec.IngressOptions.AllowedClasses = ingressClass + in.Spec.IngressOptions.AllowedClasses = &api.SelectorAllowedListSpec{ + AllowedListSpec: *ingressClass, + } } if hostnames := src.Spec.IngressOptions.AllowedHostnames; hostnames != nil { @@ -116,7 +122,12 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { in.Spec.ResourceQuota = src.Spec.ResourceQuota in.Spec.AdditionalRoleBindings = src.Spec.AdditionalRoleBindings in.Spec.ImagePullPolicies = src.Spec.ImagePullPolicies - in.Spec.PriorityClasses = src.Spec.PriorityClasses + + if src.Spec.PriorityClasses != nil { + in.Spec.PriorityClasses = &api.SelectorAllowedListSpec{ + AllowedListSpec: *src.Spec.PriorityClasses, + } + } if v, found := annotations["capsule.clastix.io/cordon"]; found { value, err := strconv.ParseBool(v) @@ -207,12 +218,14 @@ func (in *Tenant) ConvertTo(raw conversion.Hub) error { } dst.Spec.ServiceOptions = in.Spec.ServiceOptions - dst.Spec.StorageClasses = in.Spec.StorageClasses + if in.Spec.StorageClasses != nil { + dst.Spec.StorageClasses = &in.Spec.StorageClasses.AllowedListSpec + } dst.Spec.IngressOptions.HostnameCollisionScope = in.Spec.IngressOptions.HostnameCollisionScope if allowed := in.Spec.IngressOptions.AllowedClasses; allowed != nil { - dst.Spec.IngressOptions.AllowedClasses = allowed + dst.Spec.IngressOptions.AllowedClasses = &allowed.AllowedListSpec } if allowed := in.Spec.IngressOptions.AllowedHostnames; allowed != nil { @@ -231,7 +244,10 @@ func (in *Tenant) ConvertTo(raw conversion.Hub) error { dst.Spec.ResourceQuota = in.Spec.ResourceQuota dst.Spec.AdditionalRoleBindings = in.Spec.AdditionalRoleBindings dst.Spec.ImagePullPolicies = in.Spec.ImagePullPolicies - dst.Spec.PriorityClasses = in.Spec.PriorityClasses + + if in.Spec.PriorityClasses != nil { + dst.Spec.PriorityClasses = &in.Spec.PriorityClasses.AllowedListSpec + } if in.Spec.PreventDeletion { annotations[api.ProtectedTenantAnnotation] = "true" //nolint:goconst diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index 47f3237b..02001ae5 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -18,7 +18,7 @@ type TenantSpec struct { // Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. ServiceOptions *api.ServiceOptions `json:"serviceOptions,omitempty"` // Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. - StorageClasses *api.AllowedListSpec `json:"storageClasses,omitempty"` + StorageClasses *api.SelectorAllowedListSpec `json:"storageClasses,omitempty"` // Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. IngressOptions IngressOptions `json:"ingressOptions,omitempty"` // Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. @@ -36,7 +36,7 @@ type TenantSpec struct { // Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. ImagePullPolicies []api.ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` // Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. - PriorityClasses *api.AllowedListSpec `json:"priorityClasses,omitempty"` + PriorityClasses *api.SelectorAllowedListSpec `json:"priorityClasses,omitempty"` // Toggling the Tenant resources cordoning, when enable resources cannot be deleted. Cordoned bool `json:"cordoned,omitempty"` // Prevent accidental deletion of the Tenant. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 06023ba2..248feb8f 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -261,7 +261,7 @@ func (in *IngressOptions) DeepCopyInto(out *IngressOptions) { *out = *in if in.AllowedClasses != nil { in, out := &in.AllowedClasses, &out.AllowedClasses - *out = new(api.AllowedListSpec) + *out = new(api.SelectorAllowedListSpec) (*in).DeepCopyInto(*out) } if in.AllowedHostnames != nil { @@ -718,7 +718,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.StorageClasses != nil { in, out := &in.StorageClasses, &out.StorageClasses - *out = new(api.AllowedListSpec) + *out = new(api.SelectorAllowedListSpec) (*in).DeepCopyInto(*out) } in.IngressOptions.DeepCopyInto(&out.IngressOptions) @@ -751,7 +751,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.PriorityClasses != nil { in, out := &in.PriorityClasses, &out.PriorityClasses - *out = new(api.AllowedListSpec) + *out = new(api.SelectorAllowedListSpec) (*in).DeepCopyInto(*out) } } diff --git a/pkg/api/allowed_list.go b/pkg/api/allowed_list.go index 3bff873d..8d0a9e4f 100644 --- a/pkg/api/allowed_list.go +++ b/pkg/api/allowed_list.go @@ -7,10 +7,31 @@ import ( "regexp" "sort" "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" ) // +kubebuilder:object:generate=true +type SelectorAllowedListSpec struct { + AllowedListSpec `json:",inline"` + + Selector metav1.LabelSelector `json:",inline"` +} + +func (in *SelectorAllowedListSpec) SelectorMatch(obj client.Object) bool { + selector, err := metav1.LabelSelectorAsSelector(&in.Selector) + if err != nil { + return false + } + + return selector.Matches(labels.Set(obj.GetLabels())) +} + +// +kubebuilder:object:generate=true + type AllowedListSpec struct { Exact []string `json:"allowed,omitempty"` Regex string `json:"allowedRegex,omitempty"` diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go index 139a7ec8..d6545982 100644 --- a/pkg/api/zz_generated.deepcopy.go +++ b/pkg/api/zz_generated.deepcopy.go @@ -219,6 +219,23 @@ func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SelectorAllowedListSpec) DeepCopyInto(out *SelectorAllowedListSpec) { + *out = *in + in.AllowedListSpec.DeepCopyInto(&out.AllowedListSpec) + in.Selector.DeepCopyInto(&out.Selector) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelectorAllowedListSpec. +func (in *SelectorAllowedListSpec) DeepCopy() *SelectorAllowedListSpec { + if in == nil { + return nil + } + out := new(SelectorAllowedListSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) { *out = *in diff --git a/pkg/webhook/ingress/errors.go b/pkg/webhook/ingress/errors.go index 20988a52..79f6b5be 100644 --- a/pkg/webhook/ingress/errors.go +++ b/pkg/webhook/ingress/errors.go @@ -12,10 +12,10 @@ import ( type ingressClassForbiddenError struct { className string - spec api.AllowedListSpec + spec api.SelectorAllowedListSpec } -func NewIngressClassForbidden(className string, spec api.AllowedListSpec) error { +func NewIngressClassForbidden(className string, spec api.SelectorAllowedListSpec) error { return &ingressClassForbiddenError{ className: className, spec: spec, @@ -54,10 +54,10 @@ func (i ingressHostnameNotValidError) Error() string { } type ingressClassNotValidError struct { - spec api.AllowedListSpec + spec api.SelectorAllowedListSpec } -func NewIngressClassNotValid(spec api.AllowedListSpec) error { +func NewIngressClassNotValid(spec api.SelectorAllowedListSpec) error { return &ingressClassNotValidError{ spec: spec, } @@ -68,7 +68,7 @@ func (i ingressClassNotValidError) Error() string { } // nolint:predeclared -func appendClassError(spec api.AllowedListSpec) (append string) { +func appendClassError(spec api.SelectorAllowedListSpec) (append string) { if len(spec.Exact) > 0 { append += fmt.Sprintf(", one of the following (%s)", strings.Join(spec.Exact, ", ")) } @@ -77,6 +77,10 @@ func appendClassError(spec api.AllowedListSpec) (append string) { append += fmt.Sprintf(", or matching the regex %s", spec.Regex) } + if len(spec.Selector.MatchLabels) > 0 || len(spec.Selector.MatchExpressions) > 0 { + append += fmt.Sprintf(", or matching the label selector defined in the Tenant") + } + return } diff --git a/pkg/webhook/ingress/validate_class.go b/pkg/webhook/ingress/validate_class.go index 34f79fed..93021072 100644 --- a/pkg/webhook/ingress/validate_class.go +++ b/pkg/webhook/ingress/validate_class.go @@ -5,9 +5,15 @@ package ingress import ( "context" + "net/http" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/version" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -20,10 +26,43 @@ import ( type class struct { configuration configuration.Configuration + version *version.Version } func Class(configuration configuration.Configuration) capsulewebhook.Handler { - return &class{configuration: configuration} + version, _ := utils.GetK8sVersion() + + return &class{ + configuration: configuration, + version: version, + } +} + +func (r *class) retrieveIngressClass(ctx context.Context, ctrlClient client.Client, ingressClassName *string) (client.Object, error) { + if r.version == nil || ingressClassName == nil { + return nil, nil + } + + var obj client.Object + + switch { + case r.version.Minor() < 18: + return nil, nil + case r.version.Minor() < 19: + obj = &networkingv1beta1.IngressClass{} + default: + obj = &networkingv1.IngressClass{} + } + + if err := ctrlClient.Get(ctx, types.NamespacedName{Name: *ingressClassName}, obj); err != nil { + if k8serrors.IsNotFound(err) { + return nil, nil + } + + return nil, err + } + + return obj, nil } // nolint:dupl @@ -45,7 +84,14 @@ func (r *class) OnCreate(client client.Client, decoder *admission.Decoder, recor return nil } - if err = r.validateClass(*tenant, ingress.IngressClass()); err == nil { + ic, err := r.retrieveIngressClass(ctx, client, ingress.IngressClass()) + if err != nil { + response := admission.Errored(http.StatusInternalServerError, err) + + return &response + } + + if err = r.validateClass(*tenant, ingress.IngressClass(), ic); err == nil { return nil } @@ -86,7 +132,14 @@ func (r *class) OnUpdate(client client.Client, decoder *admission.Decoder, recor return nil } - if err = r.validateClass(*tenant, ingress.IngressClass()); err == nil { + ic, err := r.retrieveIngressClass(ctx, client, ingress.IngressClass()) + if err != nil { + response := admission.Errored(http.StatusInternalServerError, err) + + return &response + } + + if err = r.validateClass(*tenant, ingress.IngressClass(), ic); err == nil { return nil } @@ -114,7 +167,7 @@ func (r *class) OnDelete(client.Client, *admission.Decoder, record.EventRecorder } } -func (r *class) validateClass(tenant capsulev1beta2.Tenant, ingressClass *string) error { +func (r *class) validateClass(tenant capsulev1beta2.Tenant, ingressClass *string, ingressClassObj client.Object) error { if tenant.Spec.IngressOptions.AllowedClasses == nil { return nil } @@ -123,15 +176,21 @@ func (r *class) validateClass(tenant capsulev1beta2.Tenant, ingressClass *string return NewIngressClassNotValid(*tenant.Spec.IngressOptions.AllowedClasses) } - var valid, matched bool + var valid, regex, match bool if len(tenant.Spec.IngressOptions.AllowedClasses.Exact) > 0 { valid = tenant.Spec.IngressOptions.AllowedClasses.ExactMatch(*ingressClass) } - matched = tenant.Spec.IngressOptions.AllowedClasses.RegexMatch(*ingressClass) + regex = tenant.Spec.IngressOptions.AllowedClasses.RegexMatch(*ingressClass) + + if ingressClassObj != nil { + match = tenant.Spec.IngressOptions.AllowedClasses.SelectorMatch(ingressClassObj) + } else { + match = true + } - if !valid && !matched { + if !valid && !regex && !match { return NewIngressClassForbidden(*ingressClass, *tenant.Spec.IngressOptions.AllowedClasses) } diff --git a/pkg/webhook/pod/priorityclass.go b/pkg/webhook/pod/priorityclass.go index 7a2515d0..f431b900 100644 --- a/pkg/webhook/pod/priorityclass.go +++ b/pkg/webhook/pod/priorityclass.go @@ -5,9 +5,13 @@ package pod import ( "context" + "net/http" corev1 "k8s.io/api/core/v1" + schedulingv1 "k8s.io/api/scheduling/v1" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -23,6 +27,24 @@ func PriorityClass() capsulewebhook.Handler { return &priorityClass{} } +func (h *priorityClass) class(ctx context.Context, c client.Client, name string) (client.Object, error) { + if len(name) == 0 { + return nil, nil + } + + obj := &schedulingv1.PriorityClass{} + + if err := c.Get(ctx, types.NamespacedName{Name: name}, obj); err != nil { + if errors.IsNotFound(err) { + return nil, nil + } + + return nil, err + } + + return obj, nil +} + func (h *priorityClass) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { pod := &corev1.Pod{} @@ -46,6 +68,13 @@ func (h *priorityClass) OnCreate(c client.Client, decoder *admission.Decoder, re priorityClassName := pod.Spec.PriorityClassName + class, err := h.class(ctx, c, priorityClassName) + if err != nil { + response := admission.Errored(http.StatusInternalServerError, err) + + return &response + } + switch { case allowed == nil: // Enforcement is not in place, skipping it at all @@ -53,7 +82,7 @@ func (h *priorityClass) OnCreate(c client.Client, decoder *admission.Decoder, re case len(priorityClassName) == 0: // We don't have to force Pod to specify a Priority Class return nil - case !allowed.ExactMatch(priorityClassName) && !allowed.RegexMatch(priorityClassName): + case !allowed.ExactMatch(priorityClassName) && !allowed.RegexMatch(priorityClassName) && !allowed.SelectorMatch(class): recorder.Eventf(&tntList.Items[0], corev1.EventTypeWarning, "ForbiddenPriorityClass", "Pod %s/%s is using Priority Class %s is forbidden for the current Tenant", pod.Namespace, pod.Name, priorityClassName) response := admission.Denied(NewPodPriorityClassForbidden(priorityClassName, *allowed).Error()) diff --git a/pkg/webhook/pod/priorityclass_errors.go b/pkg/webhook/pod/priorityclass_errors.go index 21b91d33..2d439c92 100644 --- a/pkg/webhook/pod/priorityclass_errors.go +++ b/pkg/webhook/pod/priorityclass_errors.go @@ -12,10 +12,10 @@ import ( type podPriorityClassForbiddenError struct { priorityClassName string - spec api.AllowedListSpec + spec api.SelectorAllowedListSpec } -func NewPodPriorityClassForbidden(priorityClassName string, spec api.AllowedListSpec) error { +func NewPodPriorityClassForbidden(priorityClassName string, spec api.SelectorAllowedListSpec) error { return &podPriorityClassForbiddenError{ priorityClassName: priorityClassName, spec: spec, @@ -35,6 +35,10 @@ func (f podPriorityClassForbiddenError) Error() (err string) { extra = append(extra, fmt.Sprintf(" use one matching the following regex (%s)", f.spec.Regex)) } + if len(f.spec.Selector.MatchLabels) > 0 || len(f.spec.Selector.MatchExpressions) > 0 { + extra = append(extra, ", or matching the label selector defined in the Tenant") + } + err += strings.Join(extra, " or ") return diff --git a/pkg/webhook/pvc/errors.go b/pkg/webhook/pvc/errors.go index 196f6b06..dc83403e 100644 --- a/pkg/webhook/pvc/errors.go +++ b/pkg/webhook/pvc/errors.go @@ -11,17 +11,17 @@ import ( ) type storageClassNotValidError struct { - spec api.AllowedListSpec + spec api.SelectorAllowedListSpec } -func NewStorageClassNotValid(storageClasses api.AllowedListSpec) error { +func NewStorageClassNotValid(storageClasses api.SelectorAllowedListSpec) error { return &storageClassNotValidError{ spec: storageClasses, } } // nolint:predeclared -func appendError(spec api.AllowedListSpec) (append string) { +func appendError(spec api.SelectorAllowedListSpec) (append string) { if len(spec.Exact) > 0 { append += fmt.Sprintf(", one of the following (%s)", strings.Join(spec.Exact, ", ")) } @@ -30,6 +30,10 @@ func appendError(spec api.AllowedListSpec) (append string) { append += fmt.Sprintf(", or matching the regex %s", spec.Regex) } + if len(spec.Selector.MatchLabels) > 0 || len(spec.Selector.MatchExpressions) > 0 { + append += ", or matching the label selector defined in the Tenant" + } + return } @@ -39,10 +43,10 @@ func (s storageClassNotValidError) Error() (err string) { type storageClassForbiddenError struct { className string - spec api.AllowedListSpec + spec api.SelectorAllowedListSpec } -func NewStorageClassForbidden(className string, storageClasses api.AllowedListSpec) error { +func NewStorageClassForbidden(className string, storageClasses api.SelectorAllowedListSpec) error { return &storageClassForbiddenError{ className: className, spec: storageClasses, diff --git a/pkg/webhook/pvc/validating.go b/pkg/webhook/pvc/validating.go index 8a54bfa8..4014c949 100644 --- a/pkg/webhook/pvc/validating.go +++ b/pkg/webhook/pvc/validating.go @@ -5,9 +5,13 @@ package pvc import ( "context" + "net/http" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/storage/v1" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -23,10 +27,22 @@ func Handler() capsulewebhook.Handler { return &handler{} } +func (h *handler) getStorageClass(ctx context.Context, c client.Client, name string) (client.Object, error) { + obj := &v1.StorageClass{} + + if err := c.Get(ctx, types.NamespacedName{Name: name}, obj); err != nil { + if errors.IsNotFound(err) { + return nil, nil + } + + return nil, err + } + + return obj, nil +} + func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - var valid, matched bool - pvc := &corev1.PersistentVolumeClaim{} if err := decoder.Decode(req, pvc); err != nil { return utils.ErroredResponse(err) @@ -58,7 +74,25 @@ func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder } sc := *pvc.Spec.StorageClassName - if valid, matched = tnt.Spec.StorageClasses.ExactMatch(sc), tnt.Spec.StorageClasses.RegexMatch(sc); !valid && !matched { + + scObj, err := h.getStorageClass(ctx, c, sc) + if err != nil { + response := admission.Errored(http.StatusInternalServerError, err) + + return &response + } + + var valid, regex, match bool + + valid, regex = tnt.Spec.StorageClasses.ExactMatch(sc), tnt.Spec.StorageClasses.RegexMatch(sc) + + if scObj != nil { + match = tnt.Spec.StorageClasses.SelectorMatch(scObj) + } else { + match = true + } + + if !valid && !regex && !match { recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenStorageClass", "PersistentVolumeClaim %s/%s StorageClass %s is forbidden for the current Tenant", req.Namespace, req.Name, sc) response := admission.Denied(NewStorageClassForbidden(*pvc.Spec.StorageClassName, *tnt.Spec.StorageClasses).Error()) From 462d7213b9582e32a7a5bfe010a0d701bc41efc0 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 15:32:38 +0100 Subject: [PATCH 045/153] refactor(e2e): label selector for storage, ingress, podpriority classes --- e2e/ingress_class_extensions_test.go | 12 +++++++----- e2e/ingress_class_networking_test.go | 12 +++++++----- e2e/owner_webhooks_test.go | 10 ++++++---- e2e/pod_priority_class_test.go | 8 +++++--- e2e/storage_class_test.go | 12 +++++++----- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index 0ffaa691..e17b205e 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -35,12 +35,14 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }, }, IngressOptions: capsulev1beta2.IngressOptions{ - AllowedClasses: &api.AllowedListSpec{ - Exact: []string{ - "nginx", - "haproxy", + AllowedClasses: &api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{ + "nginx", + "haproxy", + }, + Regex: "^oil-.*$", }, - Regex: "^oil-.*$", }, }, }, diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index 500a1cbf..0ecdd67b 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -34,12 +34,14 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" }, }, IngressOptions: capsulev1beta2.IngressOptions{ - AllowedClasses: &api.AllowedListSpec{ - Exact: []string{ - "nginx", - "haproxy", + AllowedClasses: &api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{ + "nginx", + "haproxy", + }, + Regex: "^oil-.*$", }, - Regex: "^oil-.*$", }, }, }, diff --git a/e2e/owner_webhooks_test.go b/e2e/owner_webhooks_test.go index 55e90e9d..b20772b6 100644 --- a/e2e/owner_webhooks_test.go +++ b/e2e/owner_webhooks_test.go @@ -33,10 +33,12 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { Kind: "User", }, }, - StorageClasses: &api.AllowedListSpec{ - Exact: []string{ - "cephfs", - "glusterfs", + StorageClasses: &api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{ + "cephfs", + "glusterfs", + }, }, }, LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index 93300ee0..fd774d72 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -30,9 +30,11 @@ var _ = Describe("enforcing a Priority Class", func() { Kind: "User", }, }, - PriorityClasses: &api.AllowedListSpec{ - Exact: []string{"gold"}, - Regex: "pc\\-\\w+", + PriorityClasses: &api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{"gold"}, + Regex: "pc\\-\\w+", + }, }, }, } diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go index 7cd27dcc..2469a871 100644 --- a/e2e/storage_class_test.go +++ b/e2e/storage_class_test.go @@ -31,12 +31,14 @@ var _ = Describe("when Tenant handles Storage classes", func() { Kind: "User", }, }, - StorageClasses: &api.AllowedListSpec{ - Exact: []string{ - "cephfs", - "glusterfs", + StorageClasses: &api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{ + "cephfs", + "glusterfs", + }, + Regex: "^oil-.*$", }, - Regex: "^oil-.*$", }, }, } From dbbd9e64c01ad30e42e6a0832ec8b7c5c9e7fb7e Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 15:33:06 +0100 Subject: [PATCH 046/153] chore(kustomize): label selector for storage, ingress, podpriority classes --- .../crd/bases/capsule.clastix.io_tenants.yaml | 123 ++++++++++++++++++ config/install.yaml | 81 ++++++++++++ 2 files changed, 204 insertions(+) diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index 7562231e..b92eaaa4 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -2056,7 +2056,48 @@ spec: type: array allowedRegex: type: string + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object type: object + x-kubernetes-map-type: atomic allowedHostnames: description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources @@ -2835,7 +2876,48 @@ spec: type: array allowedRegex: type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object type: object + x-kubernetes-map-type: atomic resourceQuotas: description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace @@ -2983,7 +3065,48 @@ spec: type: array allowedRegex: type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object type: object + x-kubernetes-map-type: atomic required: - owners type: object diff --git a/config/install.yaml b/config/install.yaml index 5a0e2ff8..2c2a4eb4 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -1948,7 +1948,34 @@ spec: type: array allowedRegex: type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object + x-kubernetes-map-type: atomic allowedHostnames: description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. properties: @@ -2423,7 +2450,34 @@ spec: type: array allowedRegex: type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object + x-kubernetes-map-type: atomic resourceQuotas: description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. properties: @@ -2534,7 +2588,34 @@ spec: type: array allowedRegex: type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object + x-kubernetes-map-type: atomic required: - owners type: object From 7c4b46eeb1c2b06c4cef0df2be596043d33fe688 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 15:33:18 +0100 Subject: [PATCH 047/153] chore(helm): label selector for storage, ingress, podpriority classes --- charts/capsule/crds/tenant-crd.yaml | 81 +++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index 90ec09ec..ef91fc79 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -1385,7 +1385,34 @@ spec: type: array allowedRegex: type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object + x-kubernetes-map-type: atomic allowedHostnames: description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. properties: @@ -1860,7 +1887,34 @@ spec: type: array allowedRegex: type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object + x-kubernetes-map-type: atomic resourceQuotas: description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. properties: @@ -1971,7 +2025,34 @@ spec: type: array allowedRegex: type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object + x-kubernetes-map-type: atomic required: - owners type: object From 0d9054db347debf1279781560d6fc74293835c99 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 15:33:25 +0100 Subject: [PATCH 048/153] docs(api): label selector for storage, ingress, podpriority classes --- docs/content/general/crds-apis.md | 162 ++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/docs/content/general/crds-apis.md b/docs/content/general/crds-apis.md index 19ac9b7f..594ac477 100644 --- a/docs/content/general/crds-apis.md +++ b/docs/content/general/crds-apis.md @@ -3284,6 +3284,60 @@ Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures tha
+ + + + + + + + + + + +
NameTypeDescriptionRequired
kindenum + Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
+
+ Enum: User, Group, ServiceAccount
+
true
namestring + Name of tenant owner.
+
true
clusterRoles[]string + Defines additional cluster-roles for the specific Owner.
+
+ Default: [admin capsule-namespace-deleter]
+
false
proxySettings []objecttrue
forceTenantPrefixboolean + Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment.
+
+ Default: false
+
false
nodeMetadata object Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes.
truefalse
overrides object Allows to set different name rather than the canonical one for the Capsule configuration objects, such as webhook secret or configurations.
-
true
forceTenantPrefixboolean - Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment.

- Default: false
+ Default: map[TLSSecretName:capsule-tls mutatingWebhookConfigurationName:capsule-mutating-webhook-configuration validatingWebhookConfigurationName:capsule-validating-webhook-configuration]
false
false
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.ingressOptions.allowedClasses.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
@@ -4358,6 +4412,60 @@ Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures th
false + + matchExpressions + []object + + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ + false + + matchLabels + map[string]string + + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+ + false + + + + +### Tenant.spec.priorityClasses.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
@@ -4678,6 +4786,60 @@ Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures tha
false + + matchExpressions + []object + + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+ + false + + matchLabels + map[string]string + + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+ + false + + + + +### Tenant.spec.storageClasses.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
From e964f340862c75b8d4b05103a6683d1ceba83c74 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Dec 2022 15:44:22 +0100 Subject: [PATCH 049/153] fix: avoiding nil pointer when empty map for labels and annotations --- pkg/webhook/namespace/user_metadata.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/webhook/namespace/user_metadata.go b/pkg/webhook/namespace/user_metadata.go index 2a90df1e..41b79d43 100644 --- a/pkg/webhook/namespace/user_metadata.go +++ b/pkg/webhook/namespace/user_metadata.go @@ -135,6 +135,14 @@ func (r *userMetadataHandler) OnUpdate(client client.Client, decoder *admission. labels, annotations := oldNs.GetLabels(), oldNs.GetAnnotations() + if labels == nil { + labels = make(map[string]string) + } + + if annotations == nil { + annotations = make(map[string]string) + } + for key, value := range newNs.GetLabels() { v, ok := labels[key] if !ok { From ee0fdc9efa050528846e80ad6d479be70e0cf7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Tue, 27 Dec 2022 15:51:28 +0100 Subject: [PATCH 050/153] feat: ct install performs locally & ci loads current capsule build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- .github/workflows/helm.yml | 38 ++++++++++++++++++------------ .gitignore | 3 --- Makefile | 19 +++++++++++++-- charts/capsule/ci/test-values.yaml | 9 +++++++ 4 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 charts/capsule/ci/test-values.yaml diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index d26d9fea..69a7b0ae 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -42,23 +42,31 @@ jobs: else echo -e '\033[0;32mDocumentation up to date\033[0m ✔' fi - # Create KIND Cluster - - name: Create kind cluster - uses: helm/kind-action@v1.2.0 - if: steps.list-changed.outputs.changed == 'true' - # Install Required Operators/CRDs - - name: Prepare Cluster Operators/CRDs - run: | - # Cert-Manager CRDs - kubectl create -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.crds.yaml - - # Prometheus CRDs - kubectl create -f https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.58.0/bundle.yaml - if: steps.list-changed.outputs.changed == 'true' - # Install Charts + + # ATTENTION: This is a workaround for the upcoming ApiVersion Conversions for the capsule CRDs + # With this workflow the current docker image is build and loaded into kind, otherwise the install fails + # In the future this must be removed and the chart-testing-action must be used - name: Run chart-testing (install) - run: ct install --debug --config ./.github/configs/ct.yaml + run: make helm-test if: steps.list-changed.outputs.changed == 'true' + + ## Create KIND Cluster + #- name: Create kind cluster + # uses: helm/kind-action@v1.2.0 + # if: steps.list-changed.outputs.changed == 'true' + ## Install Required Operators/CRDs + #- name: Prepare Cluster Operators/CRDs + # run: | + # # Cert-Manager CRDs + # kubectl create -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.crds.yaml + # + # # Prometheus CRDs + # kubectl create -f https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.58.0/bundle.yaml + # if: steps.list-changed.outputs.changed == 'true' + ## Install Charts + #- name: Run chart-testing (install) + # run: ct install --debug --config ./.github/configs/ct.yaml + # if: steps.list-changed.outputs.changed == 'true' release: if: startsWith(github.ref, 'refs/tags/helm-v') runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index d7c49bbe..b9a90ae4 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,3 @@ bin **/*.key .DS_Store *.tgz - -capsule - diff --git a/Makefile b/Makefile index b2d08df0..419f9664 100644 --- a/Makefile +++ b/Makefile @@ -86,8 +86,15 @@ helm-docs: HELMDOCS_VERSION := v1.11.0 helm-docs: docker @docker run -v "$(SRC_ROOT):/helm-docs" jnorwood/helm-docs:$(HELMDOCS_VERSION) --chart-search-root /helm-docs -helm-lint: docker - @docker run -v "$(SRC_ROOT):/workdir" --entrypoint /bin/sh quay.io/helmpack/chart-testing:v3.3.1 -c "cd /workdir && ct lint --config .github/configs/ct.yaml --lint-conf .github/configs/lintconf.yaml --all --debug" +helm-lint: ct + @ct lint --config $(SRC_ROOT)/.github/configs/ct.yaml --lint-conf $(SRC_ROOT)/.github/configs/lintconf.yaml --all --debug + +helm-test: kind ct docker-build + @kind create cluster --wait=60s --name capsule-charts + @kind load docker-image --name capsule-charts ${IMG} + @kubectl create ns capsule-system + @ct install --config $(SRC_ROOT)/.github/configs/ct.yaml --namespace=capsule-system --all --debug + @kind delete cluster --name capsule-charts docker: @hash docker 2>/dev/null || {\ @@ -172,6 +179,14 @@ GINKGO = $(shell pwd)/bin/ginkgo ginkgo: ## Download ginkgo locally if necessary. $(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/ginkgo@v1.16.5) +CT = $(shell pwd)/bin/ct +ct: ## Download ct locally if necessary. + $(call go-install-tool,$(CT),github.com/helm/chart-testing/v3/ct@v3.7.1) + +KIND = $(shell pwd)/bin/kind +kind: ## Download kind locally if necessary. + $(call go-install-tool,$(KIND),sigs.k8s.io/kind/cmd/kind@v0.17.0) + KUSTOMIZE = $(shell pwd)/bin/kustomize kustomize: ## Download kustomize locally if necessary. $(call install-kustomize,$(KUSTOMIZE),3.8.7) diff --git a/charts/capsule/ci/test-values.yaml b/charts/capsule/ci/test-values.yaml new file mode 100644 index 00000000..54daa068 --- /dev/null +++ b/charts/capsule/ci/test-values.yaml @@ -0,0 +1,9 @@ +fullnameOverride: capsule +manager: + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 200m + memory: 128Mi From 79391f863ac28f0a8764e95b88e17f38b06ce90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Wed, 28 Dec 2022 15:01:28 +0100 Subject: [PATCH 051/153] feat: add runtimeclass control Signed-off-by: Oliver Baehler --- Makefile | 2 +- api/v1beta2/tenant_types.go | 2 + api/v1beta2/zz_generated.deepcopy.go | 5 + charts/capsule/crds/tenant-crd.yaml | 4888 ++++++++++------- .../crd/bases/capsule.clastix.io_tenants.yaml | 53 + config/install.yaml | 37 + docs/content/general/crds-apis.md | 94 + docs/content/general/tutorial.md | 36 + e2e/pod_priority_class_test.go | 59 +- e2e/pod_runtime_class_test.go | 222 + main.go | 2 +- pkg/webhook/pod/priorityclass_errors.go | 20 +- pkg/webhook/pod/runtimeclass.go | 108 + pkg/webhook/pod/runtimeclass_errors.go | 29 + pkg/webhook/utils/error.go | 23 + 15 files changed, 3667 insertions(+), 1913 deletions(-) create mode 100644 e2e/pod_runtime_class_test.go create mode 100644 pkg/webhook/pod/runtimeclass.go create mode 100644 pkg/webhook/pod/runtimeclass_errors.go diff --git a/Makefile b/Makefile index 419f9664..fbf209e2 100644 --- a/Makefile +++ b/Makefile @@ -257,7 +257,7 @@ e2e-build/%: capsule \ ./charts/capsule -e2e-exec: +e2e-exec: ginkgo $(GINKGO) -v -tags e2e ./e2e e2e-destroy: diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index 02001ae5..2d9ac06c 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -35,6 +35,8 @@ type TenantSpec struct { AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` // Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. ImagePullPolicies []api.ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` + // Specifies the allowed RuntimeClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. Optional. + RuntimeClasses *api.SelectorAllowedListSpec `json:"runtimeClasses,omitempty"` // Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. PriorityClasses *api.SelectorAllowedListSpec `json:"priorityClasses,omitempty"` // Toggling the Tenant resources cordoning, when enable resources cannot be deleted. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 248feb8f..2d044ffb 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -749,6 +749,11 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { *out = make([]api.ImagePullPolicySpec, len(*in)) copy(*out, *in) } + if in.RuntimeClasses != nil { + in, out := &in.RuntimeClasses, &out.RuntimeClasses + *out = new(api.SelectorAllowedListSpec) + (*in).DeepCopyInto(*out) + } if in.PriorityClasses != nil { in, out := &in.PriorityClasses, &out.PriorityClasses *out = new(api.SelectorAllowedListSpec) diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index ef91fc79..f783ba48 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -1,2085 +1,3193 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null name: tenants.capsule.clastix.io spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - name: capsule-webhook-service - namespace: capsule-system - path: /convert - conversionReviewVersions: - - v1alpha1 - - v1beta1 - - v1beta2 group: capsule.clastix.io names: kind: Tenant listKind: TenantList plural: tenants shortNames: - - tnt + - tnt singular: tenant scope: Cluster versions: - - additionalPrinterColumns: - - description: The max amount of Namespaces can be created - jsonPath: .spec.namespaceQuota - name: Namespace quota - type: integer - - description: The total amount of Namespaces in use - jsonPath: .status.size - name: Namespace count - type: integer - - description: The assigned Tenant owner - jsonPath: .spec.owner.name - name: Owner name - type: string - - description: The assigned Tenant owner kind - jsonPath: .spec.owner.kind - name: Owner kind - type: string - - description: Node Selector applied to Pods - jsonPath: .spec.nodeSelector - name: Node selector - type: string - - description: Age - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version. - name: v1alpha1 - schema: - openAPIV3Schema: - description: Tenant is the Schema for the tenants API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TenantSpec defines the desired state of Tenant. - properties: - additionalRoleBindings: - items: - properties: - clusterRoleName: - type: string - subjects: - description: kubebuilder:validation:Minimum=1 - items: - description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. - properties: - apiGroup: - description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. - type: string - kind: - description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. - type: string - name: - description: Name of the object being referenced. - type: string - namespace: - description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - type: array - required: - - clusterRoleName - - subjects - type: object - type: array - containerRegistries: + - additionalPrinterColumns: + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceQuota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: The assigned Tenant owner + jsonPath: .spec.owner.name + name: Owner name + type: string + - description: The assigned Tenant owner kind + jsonPath: .spec.owner.kind + name: Owner kind + type: string + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: This version is going to be dropped in the upcoming version + of Capsule; please, migrate to v1beta2 version. + name: v1alpha1 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + items: properties: - allowed: - items: - type: string - type: array - allowedRegex: + clusterRoleName: type: string - type: object - externalServiceIPs: - properties: - allowed: + subjects: + description: kubebuilder:validation:Minimum=1 items: - pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ - type: string + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic type: array required: - - allowed + - clusterRoleName + - subjects type: object - ingressClasses: + type: array + containerRegistries: + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + externalServiceIPs: + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string + type: array + required: + - allowed + type: object + ingressClasses: + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + ingressHostnames: + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + limitRanges: + items: + description: LimitRangeSpec defines a min/max usage limit for resources + that match on kind. properties: - allowed: + limits: + description: Limits is the list of LimitRangeItem objects that + are enforced. items: - type: string + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value + by resource name if resource limit is omitted. + type: object + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource requirement + request value by resource name if resource request is + omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by resource + name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the named + resource must have a request and limit that are both + non-zero where limit divided by request is less than + or equal to the enumerated value; this represents the + max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by resource + name. + type: object + type: + description: Type of resource that this limit applies + to. + type: string + required: + - type + type: object type: array - allowedRegex: - type: string + required: + - limits type: object - ingressHostnames: + type: array + namespaceQuota: + format: int32 + minimum: 1 + type: integer + namespacesMetadata: + properties: + additionalAnnotations: + additionalProperties: + type: string + type: object + additionalLabels: + additionalProperties: + type: string + type: object + type: object + networkPolicies: + items: + description: NetworkPolicySpec provides the specification of a NetworkPolicy properties: - allowed: + egress: + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows the + traffic), OR if the traffic matches at least one egress rule + across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure that + the pods it selects are isolated by default). This field is + beta-level in 1.8 items: - type: string - type: array - allowedRegex: - type: string - type: object - limitRanges: - items: - description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. - properties: - limits: - description: Limits is the list of LimitRangeItem objects that are enforced. - items: - description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. - properties: - default: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Default resource requirement limit value by resource name if resource limit is omitted. - type: object - defaultRequest: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. - type: object - max: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource name. - type: object - maxLimitRequestRatio: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. - type: object - min: - additionalProperties: - anyOf: + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by a + NetworkPolicySpec's podSelector. The traffic must match + both ports and to. This type is beta-level in 1.8 + properties: + ports: + description: List of destination ports for outgoing traffic. + Each item in this list is combined using a logical OR. + If this field is empty or missing, this rule matches + all ports (traffic not restricted by port). If this + field is present and contains at least one item, then + this rule allows traffic only if the traffic matches + at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow + traffic on + properties: + endPort: + description: If set, indicates that the range of + ports from port to endPort, inclusive, should + be allowed by the policy. This field cannot be + defined if the port field is not defined or if + the port field is defined as a named (string) + port. The endPort must be equal or greater than + port. This feature is in Beta state and is enabled + by default. It can be disabled using the Feature + Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: - type: integer - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource name. + description: The port on the given protocol. This + can either be a numerical or named port on a pod. + If this field is not provided, this matches all + port names and numbers. If present, only traffic + on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which + traffic must match. If not specified, this field + defaults to TCP. + type: string type: object - type: - description: Type of resource that this limit applies to. - type: string - required: - - type - type: object - type: array - required: - - limits - type: object - type: array - namespaceQuota: - format: int32 - minimum: 1 - type: integer - namespacesMetadata: - properties: - additionalAnnotations: - additionalProperties: - type: string - type: object - additionalLabels: - additionalProperties: - type: string - type: object - type: object - networkPolicies: - items: - description: NetworkPolicySpec provides the specification of a NetworkPolicy - properties: - egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 - items: - description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 - properties: - ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. - items: - description: NetworkPolicyPort describes a port to allow traffic on - properties: - endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. - type: string - type: object - type: array - to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. - items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. - properties: - cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: array + to: + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list are + combined using a logical OR operation. If this field + is empty or missing, this rule matches all destinations + (traffic not restricted by destination). If this field + is present and contains at least one item, this rule + allows traffic only if the traffic matches at least + one item in the to list. + items: + description: NetworkPolicyPeer describes a peer to allow + traffic to/from. Only certain combinations of fields + are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither of + the other fields can be. + properties: + cidr: + description: CIDR is a string representing the + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs that + should not be included within an IP Block + Valid examples are "192.168.1.1/24" or "2001:db9::/64" + Except values will be rejected if they are + outside the CIDR range + items: type: string - except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range - items: - type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label selector + semantics; if present but empty, it selects all + namespaces. \n If PodSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects all + Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects + Pods. This field follows standard label selector + semantics; if present but empty, it selects all + pods. \n If NamespaceSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects the + Pods matching PodSelector in the policy's own + Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - type: object - type: array - ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) - items: - description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. - properties: - from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. - items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. - properties: - cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: array + matchLabels: + additionalProperties: type: string - except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range - items: - type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the selected + pods. Traffic is allowed to a pod if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows the + traffic), OR if the traffic source is the pod's local node, + OR if the traffic matches at least one ingress rule across + all of the NetworkPolicy objects whose podSelector matches + the pod. If this field is empty then this NetworkPolicy does + not allow any traffic (and serves solely to ensure that the + pods it selects are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by a + NetworkPolicySpec's podSelector. The traffic must match + both ports and from. + properties: + from: + description: List of sources which should be able to access + the pods selected for this rule. Items in this list + are combined using a logical OR operation. If this field + is empty or missing, this rule matches all sources (traffic + not restricted by source). If this field is present + and contains at least one item, this rule allows traffic + only if the traffic matches at least one item in the + from list. + items: + description: NetworkPolicyPeer describes a peer to allow + traffic to/from. Only certain combinations of fields + are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither of + the other fields can be. + properties: + cidr: + description: CIDR is a string representing the + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs that + should not be included within an IP Block + Valid examples are "192.168.1.1/24" or "2001:db9::/64" + Except values will be rejected if they are + outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label selector + semantics; if present but empty, it selects all + namespaces. \n If PodSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects all + Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects + Pods. This field follows standard label selector + semantics; if present but empty, it selects all + pods. \n If NamespaceSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects the + Pods matching PodSelector in the policy's own + Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. - items: - description: NetworkPolicyPort describes a port to allow traffic on - properties: - endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. - type: string - type: object - type: array - type: object - type: array - podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic type: object type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 - items: - description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 - type: string - type: array - required: - - podSelector - type: object - type: array - nodeSelector: - additionalProperties: - type: string - type: object - owner: - description: OwnerSpec defines tenant owner name and kind. - properties: - kind: - enum: - - User - - Group - type: string - name: - type: string - required: - - kind - - name - type: object - resourceQuotas: - items: - description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. - properties: - hard: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' - type: object - scopeSelector: - description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. - properties: - matchExpressions: - description: A list of scope selector requirements by scope of the resources. + ports: + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in this + list is combined using a logical OR. If this field is + empty or missing, this rule matches all ports (traffic + not restricted by port). If this field is present and + contains at least one item, then this rule allows traffic + only if the traffic matches at least one port in the + list. items: - description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + description: NetworkPolicyPort describes a port to allow + traffic on properties: - operator: - description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. - type: string - scopeName: - description: The name of the scope that the selector applies to. + endPort: + description: If set, indicates that the range of + ports from port to endPort, inclusive, should + be allowed by the policy. This field cannot be + defined if the port field is not defined or if + the port field is defined as a named (string) + port. The endPort must be equal or greater than + port. This feature is in Beta state and is enabled + by default. It can be disabled using the Feature + Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This + can either be a numerical or named port on a pod. + If this field is not provided, this matches all + port names and numbers. If present, only traffic + on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which + traffic must match. If not specified, this field + defaults to TCP. type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - operator - - scopeName type: object type: array type: object - x-kubernetes-map-type: atomic - scopes: - description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. - items: - description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota - type: string - type: array - type: object - type: array - servicesMetadata: - properties: - additionalAnnotations: - additionalProperties: - type: string + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy object + applies. The array of ingress rules is applied to any pods + selected by this field. Multiple network policies can select + the same set of pods. In this case, the ingress rules for + each are combined additively. This field is NOT optional and + follows standard label selector semantics. An empty podSelector + matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. This + array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object type: object - additionalLabels: - additionalProperties: + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress section) + are assumed to affect Ingress. If you want to write an egress-only + policy, you must explicitly specify policyTypes [ "Egress" + ]. Likewise, if you want to write a policy that specifies + that no egress is allowed, you must specify a policyTypes + value that include "Egress" (since such a policy would not + include an Egress section and would otherwise default to just + [ "Ingress" ]). This field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 type: string - type: object + type: array + required: + - podSelector type: object - storageClasses: + type: array + nodeSelector: + additionalProperties: + type: string + type: object + owner: + description: OwnerSpec defines tenant owner name and kind. + properties: + kind: + enum: + - User + - Group + type: string + name: + type: string + required: + - kind + - name + type: object + resourceQuotas: + items: + description: ResourceQuotaSpec defines the desired hard limits to + enforce for Quota. properties: - allowed: + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for each + named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters like + scopes that must match each object tracked by a quota but + expressed using ScopeSelectorOperator in combination with + possible values. For a resource to match, both scopes AND + scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by scope + of the resources. + items: + description: A scoped-resource selector requirement is + a selector that contains values, a scope name, and an + operator that relates the scope name and values. + properties: + operator: + description: Represents a scope's relationship to + a set of values. Valid operators are In, NotIn, + Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector + applies to. + type: string + values: + description: An array of string values. If the operator + is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during + a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each object + tracked by a quota. If not specified, the quota matches all + objects. items: + description: A ResourceQuotaScope defines a filter that must + match each object tracked by a quota type: string type: array - allowedRegex: + type: object + type: array + servicesMetadata: + properties: + additionalAnnotations: + additionalProperties: type: string + type: object + additionalLabels: + additionalProperties: + type: string + type: object + type: object + storageClasses: + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + required: + - owner + type: object + status: + description: TenantStatus defines the observed state of Tenant. + properties: + namespaces: + items: + type: string + type: array + size: + type: integer + required: + - size + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The actual state of the Tenant + jsonPath: .status.state + name: State + type: string + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceOptions.quota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. + Capsule will ensure that all namespaces in the Tenant always contain + the RoleBinding for the given ClusterRole. Optional. + items: + properties: + clusterRoleName: + type: string + subjects: + description: kubebuilder:validation:Minimum=1 + items: + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + required: + - clusterRoleName + - subjects type: object - required: - - owner - type: object - status: - description: TenantStatus defines the observed state of Tenant. - properties: - namespaces: - items: + type: array + containerRegistries: + description: Specifies the trusted Image Registries assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed trusted registries. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: type: string - type: array - size: - type: integer - required: - - size - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: The actual state of the Tenant - jsonPath: .status.state - name: State - type: string - - description: The max amount of Namespaces can be created - jsonPath: .spec.namespaceOptions.quota - name: Namespace quota - type: integer - - description: The total amount of Namespaces in use - jsonPath: .status.size - name: Namespace count - type: integer - - description: Node Selector applied to Pods - jsonPath: .spec.nodeSelector - name: Node selector - type: string - - description: Age - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Tenant is the Schema for the tenants API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TenantSpec defines the desired state of Tenant. - properties: - additionalRoleBindings: - description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. - items: + type: object + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies + option in Pod resources. Capsule assures that all Pod resources + created in the Tenant can use only one of the allowed policy. Optional. + items: + enum: + - Always + - Never + - IfNotPresent + type: string + type: array + ingressOptions: + description: Specifies options for the Ingress resources, such as + allowed hostnames and IngressClass. Optional. + properties: + allowedClasses: + description: Specifies the allowed IngressClasses assigned to + the Tenant. Capsule assures that all Ingress resources created + in the Tenant can use only one of the allowed IngressClasses. + Optional. properties: - clusterRoleName: - type: string - subjects: - description: kubebuilder:validation:Minimum=1 + allowed: items: - description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. - properties: - apiGroup: - description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. - type: string - kind: - description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. - type: string - name: - description: Name of the object being referenced. - type: string - namespace: - description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic + type: string type: array - required: - - clusterRoleName - - subjects + allowedRegex: + type: string type: object - type: array - containerRegistries: - description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. - properties: - allowed: - items: + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for + the given Tenant. Capsule assures that all Ingress resources + created in the Tenant can use only one of the allowed hostnames. + Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: type: string - type: array - allowedRegex: - type: string - type: object - imagePullPolicies: - description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. - items: + type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed + when Tenant Owners create Ingress with allowed hostnames. \n + - Cluster: disallow the creation of an Ingress if the pair hostname + and path is already used across the Namespaces managed by Capsule. + \n - Tenant: disallow the creation of an Ingress if the pair + hostname and path is already used across the Namespaces of the + Tenant. \n - Namespace: disallow the creation of an Ingress + if the pair hostname and path is already used in the Ingress + Namespace. \n Optional." enum: - - Always - - Never - - IfNotPresent + - Cluster + - Tenant + - Namespace + - Disabled type: string - type: array - ingressOptions: - description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. - properties: - allowedClasses: - description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + type: object + limitRanges: + description: Specifies the resource min/max usage restrictions to + the Tenant. The assigned values are inherited by any namespace created + in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for + resources that match on kind. properties: - allowed: + limits: + description: Limits is the list of LimitRangeItem objects + that are enforced. items: - type: string + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value + by resource name if resource limit is omitted. + type: object + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource + requirement request value by resource name if resource + request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by + resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the + named resource must have a request and limit that + are both non-zero where limit divided by request + is less than or equal to the enumerated value; this + represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by + resource name. + type: object + type: + description: Type of resource that this limit applies + to. + type: string + required: + - type + type: object type: array - allowedRegex: - type: string + required: + - limits type: object - allowedHostnames: - description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. - properties: - allowed: - items: - type: string - type: array - allowedRegex: + type: array + type: object + namespaceOptions: + description: Specifies options for the Namespaces, such as additional + metadata or maximum number of namespaces allowed for that Tenant. + Once the namespace quota assigned to the Tenant has been reached, + the Tenant owner cannot create further namespaces. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule + operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: type: string - type: object - hostnameCollisionScope: - default: Disabled - description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." - enum: - - Cluster - - Tenant - - Namespace - - Disabled - type: string - type: object - limitRanges: - description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. - properties: + type: object + labels: + additionalProperties: + type: string + type: object + type: object + quota: + description: Specifies the maximum number of namespaces allowed + for that Tenant. Once the namespace quota assigned to the Tenant + has been reached, the Tenant owner cannot create further namespaces. + Optional. + format: int32 + minimum: 1 + type: integer + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. + The assigned NetworkPolicies are inherited by any namespace created + in the Tenant. Optional. + properties: + items: items: - items: - description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. - properties: - limits: - description: Limits is the list of LimitRangeItem objects that are enforced. - items: - description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. - properties: - default: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Default resource requirement limit value by resource name if resource limit is omitted. - type: object - defaultRequest: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. - type: object - max: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource name. - type: object - maxLimitRequestRatio: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. - type: object - min: - additionalProperties: - anyOf: + description: NetworkPolicySpec provides the specification of + a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows + the traffic), OR if the traffic matches at least one egress + rule across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure + that the pods it selects are isolated by default). This + field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and to. This type is beta-level in + 1.8 + properties: + ports: + description: List of destination ports for outgoing + traffic. Each item in this list is combined using + a logical OR. If this field is empty or missing, + this rule matches all ports (traffic not restricted + by port). If this field is present and contains + at least one item, then this rule allows traffic + only if the traffic matches at least one port in + the list. + items: + description: NetworkPolicyPort describes a port + to allow traffic on + properties: + endPort: + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: - type: integer - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource name. + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. + type: string type: object - type: - description: Type of resource that this limit applies to. - type: string - required: - - type - type: object - type: array - required: - - limits - type: object - type: array - type: object - namespaceOptions: - description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. - properties: - additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - quota: - description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. - format: int32 - minimum: 1 - type: integer - type: object - networkPolicies: - description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. - properties: - items: - items: - description: NetworkPolicySpec provides the specification of a NetworkPolicy - properties: - egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 - items: - description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 - properties: - ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. - items: - description: NetworkPolicyPort describes a port to allow traffic on - properties: - endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. - type: string - type: object - type: array - to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. - items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. - properties: - cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: array + to: + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list + are combined using a logical OR operation. If this + field is empty or missing, this rule matches all + destinations (traffic not restricted by destination). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. + properties: + cidr: + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range + items: type: string - except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range - items: - type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - type: object - type: array - ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) - items: - description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. - properties: - from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. - items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. - properties: - cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: array + matchLabels: + additionalProperties: type: string - except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range - items: - type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the + selected pods. Traffic is allowed to a pod if there are + no NetworkPolicies selecting the pod (and cluster policy + otherwise allows the traffic), OR if the traffic source + is the pod's local node, OR if the traffic matches at + least one ingress rule across all of the NetworkPolicy + objects whose podSelector matches the pod. If this field + is empty then this NetworkPolicy does not allow any traffic + (and serves solely to ensure that the pods it selects + are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and from. + properties: + from: + description: List of sources which should be able + to access the pods selected for this rule. Items + in this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all sources (traffic not restricted by source). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. + properties: + cidr: + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. - items: - description: NetworkPolicyPort describes a port to allow traffic on - properties: - endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. - type: string - type: object - type: array - type: object - type: array - podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in + this list is combined using a logical OR. If this + field is empty or missing, this rule matches all + ports (traffic not restricted by port). If this + field is present and contains at least one item, + then this rule allows traffic only if the traffic + matches at least one port in the list. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: NetworkPolicyPort describes a port + to allow traffic on properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + endPort: + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator type: object type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object type: object - x-kubernetes-map-type: atomic - policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 - items: - description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 - type: string - type: array - required: - - podSelector - type: object - type: array - type: object - nodeSelector: - additionalProperties: - type: string - description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. - type: object - owners: - description: Specifies the owners of the Tenant. Mandatory. - items: - properties: - kind: - description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" - enum: - - User - - Group - - ServiceAccount - type: string - name: - description: Name of tenant owner. - type: string - proxySettings: - description: Proxy settings for tenant owner. - items: + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy + object applies. The array of ingress rules is applied + to any pods selected by this field. Multiple network policies + can select the same set of pods. In this case, the ingress + rules for each are combined additively. This field is + NOT optional and follows standard label selector semantics. + An empty podSelector matches all pods in this namespace. properties: - kind: - enum: - - Nodes - - StorageClasses - - IngressClasses - - PriorityClasses - type: string - operations: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - enum: - - List - - Update - - Delete - type: string + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object type: array - required: - - kind - - operations + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object type: object - type: array - required: - - kind - - name - type: object - type: array - priorityClasses: - description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress + section) are assumed to affect Ingress. If you want to + write an egress-only policy, you must explicitly specify + policyTypes [ "Egress" ]. Likewise, if you want to write + a policy that specifies that no egress is allowed, you + must specify a policyTypes value that include "Egress" + (since such a policy would not include an Egress section + and would otherwise default to just [ "Ingress" ]). This + field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector + type: object + type: array + type: object + nodeSelector: + additionalProperties: + type: string + description: Specifies the label to control the placement of pods + on a given pool of worker nodes. All namespaces created within the + Tenant will have the node selector annotation. This annotation tells + the Kubernetes scheduler to place pods on the nodes having the selector + label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. + items: properties: - allowed: - items: - type: string - type: array - allowedRegex: + kind: + description: Kind of tenant owner. Possible values are "User", + "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount type: string - type: object - resourceQuotas: - description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. - properties: - items: - items: - description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. - properties: - hard: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' - type: object - scopeSelector: - description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. - properties: - matchExpressions: - description: A list of scope selector requirements by scope of the resources. - items: - description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. - properties: - operator: - description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. - type: string - scopeName: - description: The name of the scope that the selector applies to. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - operator - - scopeName - type: object - type: array - type: object - x-kubernetes-map-type: atomic - scopes: - description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + name: + description: Name of tenant owner. + type: string + proxySettings: + description: Proxy settings for tenant owner. + items: + properties: + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses + type: string + operations: items: - description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + enum: + - List + - Update + - Delete type: string type: array + required: + - kind + - operations type: object type: array - scope: - default: Tenant - description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant - enum: - - Tenant - - Namespace - type: string + required: + - kind + - name type: object - serviceOptions: - description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. - properties: - additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + type: array + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed PriorityClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned + to the Tenant. The assigned values are inherited by any namespace + created in the Tenant. The Capsule operator aggregates ResourceQuota + at Tenant level, so that the hard quota is never crossed for the + given Tenant. This permits the Tenant owner to consume resources + in the Tenant regardless of the namespace. Optional. + properties: + items: + items: + description: ResourceQuotaSpec defines the desired hard limits + to enforce for Quota. properties: - annotations: + hard: additionalProperties: - type: string + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for + each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' type: object - labels: - additionalProperties: - type: string + scopeSelector: + description: scopeSelector is also a collection of filters + like scopes that must match each object tracked by a quota + but expressed using ScopeSelectorOperator in combination + with possible values. For a resource to match, both scopes + AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by + scope of the resources. + items: + description: A scoped-resource selector requirement + is a selector that contains values, a scope name, + and an operator that relates the scope name and + values. + properties: + operator: + description: Represents a scope's relationship + to a set of values. Valid operators are In, + NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector + applies to. + type: string + values: + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array type: object - type: object - allowedServices: - description: Block or deny certain type of Services. Optional. - properties: - externalName: - default: true - description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. - type: boolean - loadBalancer: - default: true - description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. - type: boolean - nodePort: - default: true - description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. - type: boolean - type: object - externalIPs: - description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. - properties: - allowed: + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each + object tracked by a quota. If not specified, the quota + matches all objects. items: - pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + description: A ResourceQuotaScope defines a filter that + must match each object tracked by a quota type: string type: array - required: - - allowed type: object - type: object - storageClasses: - description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource + across all Namespaces in the Tenant or individually per cluster. + Default is Tenant + enum: + - Tenant + - Namespace + type: string + type: object + serviceOptions: + description: Specifies options for the Service, such as additional + metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule + operator places on any Service resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + allowedServices: + description: Block or deny certain type of Services. Optional. + properties: + externalName: + default: true + description: Specifies if ExternalName service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services + with type ClusterIP. An empty list means no IPs are allowed. + Optional. + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string + type: array + required: + - allowed + type: object + type: object + storageClasses: + description: Specifies the allowed StorageClasses assigned to the + Tenant. Capsule assures that all PersistentVolumeClaim resources + created in the Tenant can use only one of the allowed StorageClasses. + Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + required: + - owners + type: object + status: + description: Returns the observed state of the Tenant. + properties: + namespaces: + description: List of namespaces assigned to the Tenant. + items: + type: string + type: array + size: + description: How many namespaces are assigned to the Tenant. + type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values + are "Active", "Cordoned". + enum: + - Cordoned + - Active + type: string + required: + - size + - state + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The actual state of the Tenant + jsonPath: .status.state + name: State + type: string + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceOptions.quota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. + Capsule will ensure that all namespaces in the Tenant always contain + the RoleBinding for the given ClusterRole. Optional. + items: properties: - allowed: + clusterRoleName: + type: string + subjects: + description: kubebuilder:validation:Minimum=1 items: - type: string + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic type: array - allowedRegex: - type: string + required: + - clusterRoleName + - subjects type: object - required: - - owners - type: object - status: - description: Returns the observed state of the Tenant. - properties: - namespaces: - description: List of namespaces assigned to the Tenant. - items: + type: array + containerRegistries: + description: Specifies the trusted Image Registries assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed trusted registries. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: type: string - type: array - size: - description: How many namespaces are assigned to the Tenant. - type: integer - state: - default: Active - description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + type: object + cordoned: + description: Toggling the Tenant resources cordoning, when enable + resources cannot be deleted. + type: boolean + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies + option in Pod resources. Capsule assures that all Pod resources + created in the Tenant can use only one of the allowed policy. Optional. + items: enum: - - Cordoned - - Active + - Always + - Never + - IfNotPresent type: string - required: - - size - - state - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: The actual state of the Tenant - jsonPath: .status.state - name: State - type: string - - description: The max amount of Namespaces can be created - jsonPath: .spec.namespaceOptions.quota - name: Namespace quota - type: integer - - description: The total amount of Namespaces in use - jsonPath: .status.size - name: Namespace count - type: integer - - description: Node Selector applied to Pods - jsonPath: .spec.nodeSelector - name: Node selector - type: string - - description: Age - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta2 - schema: - openAPIV3Schema: - description: Tenant is the Schema for the tenants API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TenantSpec defines the desired state of Tenant. - properties: - additionalRoleBindings: - description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. - items: + type: array + ingressOptions: + description: Specifies options for the Ingress resources, such as + allowed hostnames and IngressClass. Optional. + properties: + allowWildcardHostnames: + description: Toggles the ability for Ingress resources created + in a Tenant to have a hostname wildcard. + type: boolean + allowedClasses: + description: Specifies the allowed IngressClasses assigned to + the Tenant. Capsule assures that all Ingress resources created + in the Tenant can use only one of the allowed IngressClasses. + Optional. properties: - clusterRoleName: + allowed: + items: + type: string + type: array + allowedRegex: type: string - subjects: - description: kubebuilder:validation:Minimum=1 + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. properties: - apiGroup: - description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + key: + description: key is the label key that the selector + applies to. type: string - kind: - description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. - type: string - name: - description: Name of the object being referenced. - type: string - namespace: - description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array required: - - kind - - name + - key + - operator type: object - x-kubernetes-map-type: atomic type: array - required: - - clusterRoleName - - subjects + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object type: object - type: array - containerRegistries: - description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. - properties: - allowed: - items: + x-kubernetes-map-type: atomic + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for + the given Tenant. Capsule assures that all Ingress resources + created in the Tenant can use only one of the allowed hostnames. + Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: type: string - type: array - allowedRegex: - type: string - type: object - cordoned: - description: Toggling the Tenant resources cordoning, when enable resources cannot be deleted. - type: boolean - imagePullPolicies: - description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. - items: + type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed + when Tenant Owners create Ingress with allowed hostnames. \n + - Cluster: disallow the creation of an Ingress if the pair hostname + and path is already used across the Namespaces managed by Capsule. + \n - Tenant: disallow the creation of an Ingress if the pair + hostname and path is already used across the Namespaces of the + Tenant. \n - Namespace: disallow the creation of an Ingress + if the pair hostname and path is already used in the Ingress + Namespace. \n Optional." enum: - - Always - - Never - - IfNotPresent + - Cluster + - Tenant + - Namespace + - Disabled type: string - type: array - ingressOptions: - description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. - properties: - allowWildcardHostnames: - description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. - type: boolean - allowedClasses: - description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + required: + - allowWildcardHostnames + type: object + limitRanges: + description: Specifies the resource min/max usage restrictions to + the Tenant. The assigned values are inherited by any namespace created + in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for + resources that match on kind. properties: - allowed: - items: - type: string - type: array - allowedRegex: - type: string - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + limits: + description: Limits is the list of LimitRangeItem objects + that are enforced. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + default: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value + by resource name if resource limit is omitted. + type: object + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource + requirement request value by resource name if resource + request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by + resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the + named resource must have a request and limit that + are both non-zero where limit divided by request + is less than or equal to the enumerated value; this + represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by + resource name. + type: object + type: + description: Type of resource that this limit applies + to. type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array required: - - key - - operator + - type type: object type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + required: + - limits type: object - x-kubernetes-map-type: atomic - allowedHostnames: - description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. - properties: - allowed: - items: - type: string - type: array - allowedRegex: + type: array + type: object + namespaceOptions: + description: Specifies options for the Namespaces, such as additional + metadata or maximum number of namespaces allowed for that Tenant. + Once the namespace quota assigned to the Tenant has been reached, + the Tenant owner cannot create further namespaces. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule + operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: type: string - type: object - hostnameCollisionScope: - default: Disabled - description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." - enum: - - Cluster - - Tenant - - Namespace - - Disabled - type: string - required: - - allowWildcardHostnames - type: object - limitRanges: - description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. - properties: - items: - items: - description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. - properties: - limits: - description: Limits is the list of LimitRangeItem objects that are enforced. - items: - description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. - properties: - default: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Default resource requirement limit value by resource name if resource limit is omitted. - type: object - defaultRequest: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. - type: object - max: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource name. - type: object - maxLimitRequestRatio: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. - type: object - min: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource name. - type: object - type: - description: Type of resource that this limit applies to. - type: string - required: - - type - type: object - type: array - required: - - limits type: object - type: array - type: object - namespaceOptions: - description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. - properties: - additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - forbiddenAnnotations: - description: Define the annotations that a Tenant Owner cannot set for their Namespace resources. - properties: - denied: - items: - type: string - type: array - deniedRegex: + labels: + additionalProperties: type: string - type: object - forbiddenLabels: - description: Define the labels that a Tenant Owner cannot set for their Namespace resources. - properties: - denied: - items: - type: string - type: array - deniedRegex: + type: object + type: object + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot + set for their Namespace resources. + properties: + denied: + items: type: string - type: object - quota: - description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. - format: int32 - minimum: 1 - type: integer - required: - - forbiddenAnnotations - - forbiddenLabels - type: object - networkPolicies: - description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. - properties: + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set + for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + quota: + description: Specifies the maximum number of namespaces allowed + for that Tenant. Once the namespace quota assigned to the Tenant + has been reached, the Tenant owner cannot create further namespaces. + Optional. + format: int32 + minimum: 1 + type: integer + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. + The assigned NetworkPolicies are inherited by any namespace created + in the Tenant. Optional. + properties: + items: items: - items: - description: NetworkPolicySpec provides the specification of a NetworkPolicy - properties: - egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 - items: - description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 - properties: - ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. - items: - description: NetworkPolicyPort describes a port to allow traffic on - properties: - endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. - type: string - type: object - type: array - to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. - items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. - properties: - cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" - type: string - except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range - items: - type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: NetworkPolicySpec provides the specification of + a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows + the traffic), OR if the traffic matches at least one egress + rule across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure + that the pods it selects are isolated by default). This + field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and to. This type is beta-level in + 1.8 + properties: + ports: + description: List of destination ports for outgoing + traffic. Each item in this list is combined using + a logical OR. If this field is empty or missing, + this rule matches all ports (traffic not restricted + by port). If this field is present and contains + at least one item, then this rule allows traffic + only if the traffic matches at least one port in + the list. + items: + description: NetworkPolicyPort describes a port + to allow traffic on + properties: + endPort: + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. + type: string + type: object + type: array + to: + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list + are combined using a logical OR operation. If this + field is empty or missing, this rule matches all + destinations (traffic not restricted by destination). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. + properties: + cidr: + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - type: object - type: array - ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) - items: - description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. - properties: - from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. - items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. - properties: - cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + type: array + matchLabels: + additionalProperties: type: string - except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range - items: - type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the + selected pods. Traffic is allowed to a pod if there are + no NetworkPolicies selecting the pod (and cluster policy + otherwise allows the traffic), OR if the traffic source + is the pod's local node, OR if the traffic matches at + least one ingress rule across all of the NetworkPolicy + objects whose podSelector matches the pod. If this field + is empty then this NetworkPolicy does not allow any traffic + (and serves solely to ensure that the pods it selects + are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and from. + properties: + from: + description: List of sources which should be able + to access the pods selected for this rule. Items + in this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all sources (traffic not restricted by source). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. + properties: + cidr: + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" + type: string + except: + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: array + required: + - key + - operator type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. - items: - description: NetworkPolicyPort describes a port to allow traffic on - properties: - endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. - type: string - type: object - type: array - type: object - type: array - podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in + this list is combined using a logical OR. If this + field is empty or missing, this rule matches all + ports (traffic not restricted by port). If this + field is present and contains at least one item, + then this rule allows traffic only if the traffic + matches at least one port in the list. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: NetworkPolicyPort describes a port + to allow traffic on properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + endPort: + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator type: object type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object type: object - x-kubernetes-map-type: atomic - policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 - items: - description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 - type: string - type: array - required: - - podSelector - type: object - type: array - type: object - nodeSelector: - additionalProperties: - type: string - description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. - type: object - owners: - description: Specifies the owners of the Tenant. Mandatory. - items: - properties: - clusterRoles: - default: - - admin - - capsule-namespace-deleter - description: Defines additional cluster-roles for the specific Owner. - items: - type: string - type: array - kind: - description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" - enum: - - User - - Group - - ServiceAccount - type: string - name: - description: Name of tenant owner. - type: string - proxySettings: - description: Proxy settings for tenant owner. - items: + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy + object applies. The array of ingress rules is applied + to any pods selected by this field. Multiple network policies + can select the same set of pods. In this case, the ingress + rules for each are combined additively. This field is + NOT optional and follows standard label selector semantics. + An empty podSelector matches all pods in this namespace. properties: - kind: - enum: - - Nodes - - StorageClasses - - IngressClasses - - PriorityClasses - type: string - operations: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - enum: - - List - - Update - - Delete - type: string + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object type: array - required: - - kind - - operations + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. + type: object type: object - type: array - required: - - kind - - name - type: object - type: array - preventDeletion: - description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined. - type: boolean - priorityClasses: - description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress + section) are assumed to affect Ingress. If you want to + write an egress-only policy, you must explicitly specify + policyTypes [ "Egress" ]. Likewise, if you want to write + a policy that specifies that no egress is allowed, you + must specify a policyTypes value that include "Egress" + (since such a policy would not include an Egress section + and would otherwise default to just [ "Ingress" ]). This + field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector + type: object + type: array + type: object + nodeSelector: + additionalProperties: + type: string + description: Specifies the label to control the placement of pods + on a given pool of worker nodes. All namespaces created within the + Tenant will have the node selector annotation. This annotation tells + the Kubernetes scheduler to place pods on the nodes having the selector + label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. + items: properties: - allowed: + clusterRoles: + default: + - admin + - capsule-namespace-deleter + description: Defines additional cluster-roles for the specific + Owner. items: type: string type: array - allowedRegex: + kind: + description: Kind of tenant owner. Possible values are "User", + "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of tenant owner. type: string - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + proxySettings: + description: Proxy settings for tenant owner. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + operations: items: + enum: + - List + - Update + - Delete type: string type: array required: - - key - - operator + - kind + - operations type: object type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + required: + - kind + - name type: object - x-kubernetes-map-type: atomic - resourceQuotas: - description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. - properties: + type: array + preventDeletion: + description: Prevent accidental deletion of the Tenant. When enabled, + the deletion request will be declined. + type: boolean + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed PriorityClasses. Optional. + properties: + allowed: items: - items: - description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. - properties: - hard: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' - type: object - scopeSelector: - description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. - properties: - matchExpressions: - description: A list of scope selector requirements by scope of the resources. - items: - description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. - properties: - operator: - description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. - type: string - scopeName: - description: The name of the scope that the selector applies to. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - operator - - scopeName - type: object - type: array - type: object - x-kubernetes-map-type: atomic - scopes: - description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. - items: - description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota - type: string - type: array - type: object - type: array - scope: - default: Tenant - description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant - enum: - - Tenant - - Namespace type: string - type: object - serviceOptions: - description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. - properties: - additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + type: array + allowedRegex: + type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: type: string - type: object + type: array + required: + - key + - operator type: object - allowedServices: - description: Block or deny certain type of Services. Optional. + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned + to the Tenant. The assigned values are inherited by any namespace + created in the Tenant. The Capsule operator aggregates ResourceQuota + at Tenant level, so that the hard quota is never crossed for the + given Tenant. This permits the Tenant owner to consume resources + in the Tenant regardless of the namespace. Optional. + properties: + items: + items: + description: ResourceQuotaSpec defines the desired hard limits + to enforce for Quota. properties: - externalName: - default: true - description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. - type: boolean - loadBalancer: - default: true - description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. - type: boolean - nodePort: - default: true - description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. - type: boolean + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for + each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters + like scopes that must match each object tracked by a quota + but expressed using ScopeSelectorOperator in combination + with possible values. For a resource to match, both scopes + AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by + scope of the resources. + items: + description: A scoped-resource selector requirement + is a selector that contains values, a scope name, + and an operator that relates the scope name and + values. + properties: + operator: + description: Represents a scope's relationship + to a set of values. Valid operators are In, + NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector + applies to. + type: string + values: + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each + object tracked by a quota. If not specified, the quota + matches all objects. + items: + description: A ResourceQuotaScope defines a filter that + must match each object tracked by a quota + type: string + type: array type: object - externalIPs: - description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource + across all Namespaces in the Tenant or individually per cluster. + Default is Tenant + enum: + - Tenant + - Namespace + type: string + type: object + runtimeClasses: + description: Specifies the allowed RuntimeClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed RuntimeClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. properties: - allowed: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. items: - pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ type: string type: array required: - - allowed + - key + - operator type: object - type: object - storageClasses: - description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. - properties: - allowed: - items: - type: string - type: array - allowedRegex: + type: array + matchLabels: + additionalProperties: type: string - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + serviceOptions: + description: Specifies options for the Service, such as additional + metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule + operator places on any Service resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - owners - type: object - status: - description: Returns the observed state of the Tenant. - properties: - namespaces: - description: List of namespaces assigned to the Tenant. - items: + labels: + additionalProperties: + type: string + type: object + type: object + allowedServices: + description: Block or deny certain type of Services. Optional. + properties: + externalName: + default: true + description: Specifies if ExternalName service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources + are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services + with type ClusterIP. An empty list means no IPs are allowed. + Optional. + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string + type: array + required: + - allowed + type: object + type: object + storageClasses: + description: Specifies the allowed StorageClasses assigned to the + Tenant. Capsule assures that all PersistentVolumeClaim resources + created in the Tenant can use only one of the allowed StorageClasses. + Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: type: string - type: array - size: - description: How many namespaces are assigned to the Tenant. - type: integer - state: - default: Active - description: The operational state of the Tenant. Possible values are "Active", "Cordoned". - enum: - - Cordoned - - Active + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - owners + type: object + status: + description: Returns the observed state of the Tenant. + properties: + namespaces: + description: List of namespaces assigned to the Tenant. + items: type: string - required: - - size - - state - type: object - type: object - served: true - storage: true - subresources: - status: {} + type: array + size: + description: How many namespaces are assigned to the Tenant. + type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values + are "Active", "Cordoned". + enum: + - Cordoned + - Active + type: string + required: + - size + - state + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index b92eaaa4..f783ba48 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -3003,6 +3003,59 @@ spec: - Namespace type: string type: object + runtimeClasses: + description: Specifies the allowed RuntimeClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed RuntimeClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic serviceOptions: description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. diff --git a/config/install.yaml b/config/install.yaml index 2c2a4eb4..1f8c932e 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -2536,6 +2536,43 @@ spec: - Namespace type: string type: object + runtimeClasses: + description: Specifies the allowed RuntimeClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic serviceOptions: description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. properties: diff --git a/docs/content/general/crds-apis.md b/docs/content/general/crds-apis.md index 594ac477..999b41f2 100644 --- a/docs/content/general/crds-apis.md +++ b/docs/content/general/crds-apis.md @@ -2984,6 +2984,13 @@ TenantSpec defines the desired state of Tenant. Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
false + + runtimeClasses + object + + Specifies the allowed RuntimeClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. Optional.
+ + false serviceOptions object @@ -4612,6 +4619,93 @@ A scoped-resource selector requirement is a selector that contains values, a sco +### Tenant.spec.runtimeClasses + + + +Specifies the allowed RuntimeClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. Optional. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
allowed[]string +
+
false
allowedRegexstring +
+
false
matchExpressions[]object + matchExpressions is a list of label selector requirements. The requirements are ANDed.
+
false
matchLabelsmap[string]string + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+
false
+ + +### Tenant.spec.runtimeClasses.matchExpressions[index] + + + +A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
keystring + key is the label key that the selector applies to.
+
true
operatorstring + operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+
true
values[]string + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+
false
+ + ### Tenant.spec.serviceOptions diff --git a/docs/content/general/tutorial.md b/docs/content/general/tutorial.md index 7880afea..041a47c5 100644 --- a/docs/content/general/tutorial.md +++ b/docs/content/general/tutorial.md @@ -838,6 +838,42 @@ With the said Tenant specification, Alice can create a Pod resource if `spec.pri If a Pod is going to use a non-allowed _Priority Class_, it will be rejected by the Validation Webhook enforcing it. + +## Assign Pod Runtime Classes + +Pods can be assigned different runtime classes. With the assigned runtime you can control Container Runtime Interface (CRI) is used for each pod. See [Kubernetes documentation](https://kubernetes.io/docs/concepts/containers/runtime-class/). + +To prevent misuses of Pod Runtime Classes, Bill, the cluster admin, can enforce the allowed Pod Runtime Class at tenant level: + +```yaml +kubectl apply -f - << EOF +apiVersion: capsule.clastix.io/v1beta2 +kind: Tenant +metadata: + name: oil +spec: + owners: + - name: alice + kind: User + runtimeClasses: + allowed: + - legacy + allowedRegex: "^hardened-.*$" + selector: + matchLabels: + env: "production" +EOF +``` + +With the said Tenant specification, Alice can create a Pod resource if `spec.RuntimeClasses` equals to: + +- `legacy` +- `hardened-crio` or `hardened-containerd`, since these compile the allowed regex. +- Any RuntimeClass which has the label `env` with the value `production` + +If a Pod is going to use a non-allowed _Runtime Class_, it will be rejected by the Validation Webhook enforcing it. + + ## Assign Nodes Pool Bill, the cluster admin, can dedicate a pool of worker nodes to the `oil` tenant, to isolate the tenant applications from other noisy neighbors. diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index fd774d72..fff8555c 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -8,14 +8,15 @@ package e2e import ( "context" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/api" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/scheduling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" - "github.com/clastix/capsule/pkg/api" + "strconv" + "strings" ) var _ = Describe("enforcing a Priority Class", func() { @@ -35,6 +36,11 @@ var _ = Describe("enforcing a Priority Class", func() { Exact: []string{"gold"}, Regex: "pc\\-\\w+", }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "customers", + }, + }, }, }, } @@ -155,4 +161,51 @@ var _ = Describe("enforcing a Priority Class", func() { Expect(k8sClient.Delete(context.TODO(), class)).Should(Succeed()) } }) + + It("should allow selector match", func() { + ns := NewNamespace("priority-selector-match") + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + + for i, pc := range []string{"customer-bronze", "customer-silver", "customer-gold"} { + priorityName := strings.Join([]string{pc, "-", strconv.Itoa(i)}, "") + class := &v1.PriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: pc, + Labels: map[string]string{ + "name": priorityName, + "env": "customers", + }, + }, + Description: "fake PriorityClass for e2e", + Value: int32(10000 * (i + 2)), + } + Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: pc, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + PriorityClassName: class.GetName(), + }, + } + + cs := ownerClient(tnt.Spec.Owners[0]) + + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + return err + }).Should(Succeed()) + + Expect(k8sClient.Delete(context.TODO(), class)).Should(Succeed()) + } + }) + }) diff --git a/e2e/pod_runtime_class_test.go b/e2e/pod_runtime_class_test.go new file mode 100644 index 00000000..14d7bd49 --- /dev/null +++ b/e2e/pod_runtime_class_test.go @@ -0,0 +1,222 @@ +//go:build e2e + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + "github.com/clastix/capsule/pkg/api" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + nodev1 "k8s.io/api/node/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "strconv" + "strings" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +var _ = Describe("enforcing a Runtime Class", func() { + tnt := &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "runtime-class", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ + { + Name: "george", + Kind: "User", + }, + }, + RuntimeClasses: &api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{"legacy"}, + Regex: "^hardened-.*$", + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "customers", + }, + }, + }, + }, + } + + JustBeforeEach(func() { + EventuallyCreation(func() error { + tnt.ResourceVersion = "" + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + }) + JustAfterEach(func() { + Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + }) + + It("should block non allowed Runtime Class", func() { + runtime := &nodev1.RuntimeClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "disallowed", + }, + Handler: "custom-handler", + } + Expect(k8sClient.Create(context.TODO(), runtime)).Should(Succeed()) + + defer func() { + Expect(k8sClient.Delete(context.TODO(), runtime)).Should(Succeed()) + }() + + ns := NewNamespace("rt-disallow") + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + runtimeName := "disallowed" + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "container", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + RuntimeClassName: &runtimeName, + }, + } + + cs := ownerClient(tnt.Spec.Owners[0]) + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + return err + }).ShouldNot(Succeed()) + }) + + It("should allow exact match", func() { + runtime := &nodev1.RuntimeClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "legacy", + }, + Handler: "custom-handler", + } + Expect(k8sClient.Create(context.TODO(), runtime)).Should(Succeed()) + + defer func() { + Expect(k8sClient.Delete(context.TODO(), runtime)).Should(Succeed()) + }() + + ns := NewNamespace("rt-exact-match") + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + runtimeName := "legacy" + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "container", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + RuntimeClassName: &runtimeName, + }, + } + + cs := ownerClient(tnt.Spec.Owners[0]) + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + return err + }).Should(Succeed()) + }) + + It("should allow regex match", func() { + ns := NewNamespace("rc-regex-match") + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + + for i, rt := range []string{"hardened-crio", "hardened-containerd", "hardened-dockerd"} { + runtimeName := strings.Join([]string{rt, "-", strconv.Itoa(i)}, "") + runtime := &nodev1.RuntimeClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: runtimeName, + }, + Handler: "custom-handler", + } + + Expect(k8sClient.Create(context.TODO(), runtime)).Should(Succeed()) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: rt, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + RuntimeClassName: &runtimeName, + }, + } + + cs := ownerClient(tnt.Spec.Owners[0]) + + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + return err + }).Should(Succeed()) + + Expect(k8sClient.Delete(context.TODO(), runtime)).Should(Succeed()) + } + }) + + It("should allow selector match", func() { + ns := NewNamespace("rc-selector-match") + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + + for i, rt := range []string{"customer-containerd", "customer-crio", "customer-dockerd"} { + runtimeName := strings.Join([]string{rt, "-", strconv.Itoa(i)}, "") + runtime := &nodev1.RuntimeClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: runtimeName, + Labels: map[string]string{ + "name": runtimeName, + "env": "customers", + }, + }, + Handler: "custom-handler", + } + + Expect(k8sClient.Create(context.TODO(), runtime)).Should(Succeed()) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: rt, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + RuntimeClassName: &runtimeName, + }, + } + + cs := ownerClient(tnt.Spec.Owners[0]) + + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + return err + }).Should(Succeed()) + + Expect(k8sClient.Delete(context.TODO(), runtime)).Should(Succeed()) + } + }) + +}) diff --git a/main.go b/main.go index 41bf4efb..bb901260 100644 --- a/main.go +++ b/main.go @@ -235,7 +235,7 @@ func main() { // webhooks: the order matters, don't change it and just append webhooksList := append( make([]webhook.Webhook, 0), - route.Pod(pod.ImagePullPolicy(), pod.ContainerRegistry(), pod.PriorityClass()), + route.Pod(pod.ImagePullPolicy(), pod.ContainerRegistry(), pod.PriorityClass(), pod.RuntimeClass()), route.Namespace(utils.InCapsuleGroups(cfg, namespacewebhook.PatchHandler(), namespacewebhook.QuotaHandler(), namespacewebhook.FreezeHandler(cfg), namespacewebhook.PrefixHandler(cfg), namespacewebhook.UserMetadataHandler())), route.Ingress(ingress.Class(cfg), ingress.Hostnames(cfg), ingress.Collision(cfg), ingress.Wildcard()), route.PVC(pvc.Handler()), diff --git a/pkg/webhook/pod/priorityclass_errors.go b/pkg/webhook/pod/priorityclass_errors.go index 2d439c92..64f2cf12 100644 --- a/pkg/webhook/pod/priorityclass_errors.go +++ b/pkg/webhook/pod/priorityclass_errors.go @@ -5,9 +5,9 @@ package pod import ( "fmt" - "strings" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/webhook/utils" ) type podPriorityClassForbiddenError struct { @@ -25,21 +25,5 @@ func NewPodPriorityClassForbidden(priorityClassName string, spec api.SelectorAll func (f podPriorityClassForbiddenError) Error() (err string) { err = fmt.Sprintf("Pod Priorioty Class %s is forbidden for the current Tenant: ", f.priorityClassName) - var extra []string - - if len(f.spec.Exact) > 0 { - extra = append(extra, fmt.Sprintf("use one from the following list (%s)", strings.Join(f.spec.Exact, ", "))) - } - - if len(f.spec.Regex) > 0 { - extra = append(extra, fmt.Sprintf(" use one matching the following regex (%s)", f.spec.Regex)) - } - - if len(f.spec.Selector.MatchLabels) > 0 || len(f.spec.Selector.MatchExpressions) > 0 { - extra = append(extra, ", or matching the label selector defined in the Tenant") - } - - err += strings.Join(extra, " or ") - - return + return utils.AllowedValuesErrorMessage(f.spec, err) } diff --git a/pkg/webhook/pod/runtimeclass.go b/pkg/webhook/pod/runtimeclass.go new file mode 100644 index 00000000..0de42591 --- /dev/null +++ b/pkg/webhook/pod/runtimeclass.go @@ -0,0 +1,108 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package pod + +import ( + "context" + "net/http" + + corev1 "k8s.io/api/core/v1" + nodev1 "k8s.io/api/node/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + capsulewebhook "github.com/clastix/capsule/pkg/webhook" + "github.com/clastix/capsule/pkg/webhook/utils" +) + +type runtimeClass struct{} + +func RuntimeClass() capsulewebhook.Handler { + return &runtimeClass{} +} + +func (h *runtimeClass) class(ctx context.Context, c client.Client, name string) (client.Object, error) { + if len(name) == 0 { + return nil, nil + } + + obj := &nodev1.RuntimeClass{} + if err := c.Get(ctx, types.NamespacedName{Name: name}, obj); err != nil { + return nil, err + } + + return obj, nil +} + +func (h *runtimeClass) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return h.validate(ctx, c, decoder, recorder, req) + } +} + +func (h *runtimeClass) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return nil + } +} + +func (h *runtimeClass) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return nil + } +} + +func (h *runtimeClass) validate(ctx context.Context, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder, req admission.Request) *admission.Response { + pod := &corev1.Pod{} + if err := decoder.Decode(req, pod); err != nil { + return utils.ErroredResponse(err) + } + + tntList := &capsulev1beta2.TenantList{} + + if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ + Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace), + }); err != nil { + return utils.ErroredResponse(err) + } + + if len(tntList.Items) == 0 { + return nil + } + + allowed := tntList.Items[0].Spec.RuntimeClasses + + runtimeClassName := "" + if pod.Spec.RuntimeClassName != nil { + runtimeClassName = *pod.Spec.RuntimeClassName + } + + class, err := h.class(ctx, c, runtimeClassName) + if err != nil { + response := admission.Errored(http.StatusInternalServerError, err) + + return &response + } + + switch { + case allowed == nil: + // Enforcement is not in place, skipping it at all + return nil + case len(runtimeClassName) == 0: + // We don't have to force Pod to specify a RuntimeClass + return nil + case !allowed.ExactMatch(runtimeClassName) && !allowed.RegexMatch(runtimeClassName) && !allowed.SelectorMatch(class): + recorder.Eventf(&tntList.Items[0], corev1.EventTypeWarning, "ForbiddenRuntimeClass", "Pod %s/%s is using Runtime Class %s is forbidden for the current Tenant", pod.Namespace, pod.Name, runtimeClassName) + + response := admission.Denied(NewPodRuntimeClassForbidden(runtimeClassName, *allowed).Error()) + + return &response + default: + return nil + } +} diff --git a/pkg/webhook/pod/runtimeclass_errors.go b/pkg/webhook/pod/runtimeclass_errors.go new file mode 100644 index 00000000..0a1d9c73 --- /dev/null +++ b/pkg/webhook/pod/runtimeclass_errors.go @@ -0,0 +1,29 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package pod + +import ( + "fmt" + + "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/webhook/utils" +) + +type podRuntimeClassForbiddenError struct { + runtimeClassName string + spec api.SelectorAllowedListSpec +} + +func NewPodRuntimeClassForbidden(runtimeClassName string, spec api.SelectorAllowedListSpec) error { + return &podRuntimeClassForbiddenError{ + runtimeClassName: runtimeClassName, + spec: spec, + } +} + +func (f podRuntimeClassForbiddenError) Error() (err string) { + err = fmt.Sprintf("Pod Runtime Class %s is forbidden for the current Tenant: ", f.runtimeClassName) + + return utils.AllowedValuesErrorMessage(f.spec, err) +} diff --git a/pkg/webhook/utils/error.go b/pkg/webhook/utils/error.go index b4b95f17..c64f21bd 100644 --- a/pkg/webhook/utils/error.go +++ b/pkg/webhook/utils/error.go @@ -4,9 +4,13 @@ package utils import ( + "fmt" "net/http" + "strings" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/clastix/capsule/pkg/api" ) func ErroredResponse(err error) *admission.Response { @@ -14,3 +18,22 @@ func ErroredResponse(err error) *admission.Response { return &response } + +func AllowedValuesErrorMessage(allowed api.SelectorAllowedListSpec, err string) string { + var extra []string + if len(allowed.Exact) > 0 { + extra = append(extra, fmt.Sprintf("use one from the following list (%s)", strings.Join(allowed.Exact, ", "))) + } + + if len(allowed.Regex) > 0 { + extra = append(extra, fmt.Sprintf(" use one matching the following regex (%s)", allowed.Regex)) + } + + if len(allowed.Selector.MatchLabels) > 0 || len(allowed.Selector.MatchExpressions) > 0 { + extra = append(extra, ", or matching the label selector defined in the Tenant") + } + + err += strings.Join(extra, " or ") + + return err +} From 628efbb30f6da3443be51e935c0cdbc4be2de507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 29 Dec 2022 16:07:11 +0100 Subject: [PATCH 052/153] fix: validate pods on update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- pkg/webhook/pod/containerregistry.go | 105 ++++++++++++++++----------- pkg/webhook/route/pods.go | 2 +- 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/pkg/webhook/pod/containerregistry.go b/pkg/webhook/pod/containerregistry.go index 9f576a94..aa121d5d 100644 --- a/pkg/webhook/pod/containerregistry.go +++ b/pkg/webhook/pod/containerregistry.go @@ -25,64 +25,85 @@ func ContainerRegistry() capsulewebhook.Handler { func (h *containerRegistryHandler) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - pod := &corev1.Pod{} - if err := decoder.Decode(req, pod); err != nil { - return utils.ErroredResponse(err) - } + return h.validate(ctx, c, decoder, recorder, req) + } +} - tntList := &capsulev1beta2.TenantList{} - if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace), - }); err != nil { - return utils.ErroredResponse(err) - } +func (h *containerRegistryHandler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return nil + } +} - if len(tntList.Items) == 0 { - return nil - } +// ust be validate on update events since updates to pods on spec.containers[*].image and spec.initContainers[*].image are allowed. +func (h *containerRegistryHandler) OnUpdate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return h.validate(ctx, c, decoder, recorder, req) + } +} - tnt := tntList.Items[0] +func (h *containerRegistryHandler) validate(ctx context.Context, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder, req admission.Request) *admission.Response { + pod := &corev1.Pod{} + if err := decoder.Decode(req, pod); err != nil { + return utils.ErroredResponse(err) + } - if tnt.Spec.ContainerRegistries != nil { - var valid, matched bool + tntList := &capsulev1beta2.TenantList{} + if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ + Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace), + }); err != nil { + return utils.ErroredResponse(err) + } - for _, container := range pod.Spec.Containers { - reg := NewRegistry(container.Image) + if len(tntList.Items) == 0 { + return nil + } - if len(reg.Registry()) == 0 { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "MissingFQCI", "Pod %s/%s is not using using a fully qualified container image, cannot enforce registry the current Tenant", req.Namespace, req.Name, reg.Registry()) + tnt := tntList.Items[0] - response := admission.Denied(NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries).Error()) + if tnt.Spec.ContainerRegistries != nil { + // Evaluate init containers + for _, container := range pod.Spec.InitContainers { + if response := h.VerifyContainerRegistry(recorder, req, container, tnt); response != nil { + return response + } + } - return &response - } + // Evaluate containers + for _, container := range pod.Spec.Containers { + if response := h.VerifyContainerRegistry(recorder, req, container, tnt); response != nil { + return response + } + } + } - valid = tnt.Spec.ContainerRegistries.ExactMatch(reg.Registry()) + return nil +} - matched = tnt.Spec.ContainerRegistries.RegexMatch(reg.Registry()) +func (h *containerRegistryHandler) VerifyContainerRegistry(recorder record.EventRecorder, req admission.Request, container corev1.Container, tnt capsulev1beta2.Tenant) *admission.Response { + var valid, matched bool - if !valid && !matched { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenContainerRegistry", "Pod %s/%s is using a container hosted on registry %s that is forbidden for the current Tenant", req.Namespace, req.Name, reg.Registry()) + reg := NewRegistry(container.Image) - response := admission.Denied(NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries).Error()) + if len(reg.Registry()) == 0 { + recorder.Eventf(&tnt, corev1.EventTypeWarning, "MissingFQCI", "Pod %s/%s is not using using a fully qualified container image, cannot enforce registry the current Tenant", req.Namespace, req.Name, reg.Registry()) - return &response - } - } - } + response := admission.Denied(NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries).Error()) - return nil + return &response } -} -func (h *containerRegistryHandler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - return nil - } -} + valid = tnt.Spec.ContainerRegistries.ExactMatch(reg.Registry()) -func (h *containerRegistryHandler) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - return nil + matched = tnt.Spec.ContainerRegistries.RegexMatch(reg.Registry()) + + if !valid && !matched { + recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenContainerRegistry", "Pod %s/%s is using a container hosted on registry %s that is forbidden for the current Tenant", req.Namespace, req.Name, reg.Registry()) + + response := admission.Denied(NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries).Error()) + + return &response } + + return nil } diff --git a/pkg/webhook/route/pods.go b/pkg/webhook/route/pods.go index 577d54de..e704567d 100644 --- a/pkg/webhook/route/pods.go +++ b/pkg/webhook/route/pods.go @@ -7,7 +7,7 @@ import ( capsulewebhook "github.com/clastix/capsule/pkg/webhook" ) -// +kubebuilder:webhook:path=/pods,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="",resources=pods,verbs=create,versions=v1,name=pods.capsule.clastix.io +// +kubebuilder:webhook:path=/pods,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=pods.capsule.clastix.io type pod struct { handlers []capsulewebhook.Handler From f5cd194c0565ba3ee23cba280c303cb6aa4dce33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 29 Dec 2022 16:08:33 +0100 Subject: [PATCH 053/153] chore(kustomize): validate pods on update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- config/install.yaml | 4 +++- config/manager/configuration.yaml | 3 ++- .../samples/capsule_v1beta2_capsuleconfiguration.yaml | 11 +++++++++++ config/webhook/manifests.yaml | 1 + 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 config/samples/capsule_v1beta2_capsuleconfiguration.yaml diff --git a/config/install.yaml b/config/install.yaml index 1f8c932e..8d4dcedd 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -2794,12 +2794,13 @@ spec: defaultMode: 420 secretName: capsule-tls --- -apiVersion: capsule.clastix.io/v1alpha1 +apiVersion: capsule.clastix.io/v1beta2 kind: CapsuleConfiguration metadata: name: capsule-default namespace: capsule-system spec: + enableTLSReconciler: true forceTenantPrefix: false protectedNamespaceRegex: "" userGroups: @@ -2980,6 +2981,7 @@ webhooks: - v1 operations: - CREATE + - UPDATE resources: - pods scope: Namespaced diff --git a/config/manager/configuration.yaml b/config/manager/configuration.yaml index 950a5d46..c5adef5c 100644 --- a/config/manager/configuration.yaml +++ b/config/manager/configuration.yaml @@ -1,4 +1,4 @@ -apiVersion: capsule.clastix.io/v1alpha1 +apiVersion: capsule.clastix.io/v1beta2 kind: CapsuleConfiguration metadata: name: default @@ -6,3 +6,4 @@ spec: userGroups: ["capsule.clastix.io"] forceTenantPrefix: false protectedNamespaceRegex: "" + enableTLSReconciler: true diff --git a/config/samples/capsule_v1beta2_capsuleconfiguration.yaml b/config/samples/capsule_v1beta2_capsuleconfiguration.yaml new file mode 100644 index 00000000..71b2fce1 --- /dev/null +++ b/config/samples/capsule_v1beta2_capsuleconfiguration.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: capsule.clastix.io/v1beta2 +kind: CapsuleConfiguration +metadata: + name: default +spec: + userGroups: ["capsule.clastix.io"] + forceTenantPrefix: false + protectedNamespaceRegex: "" + enableTLSReconciler: true + diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 4567309d..db05648a 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -151,6 +151,7 @@ webhooks: - v1 operations: - CREATE + - UPDATE resources: - pods sideEffects: None From 34cfb16ea396e5811001aa60a39bb10a0335b47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 29 Dec 2022 16:09:12 +0100 Subject: [PATCH 054/153] chore(helm): validate pods on update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- charts/capsule/templates/validatingwebhookconfiguration.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/charts/capsule/templates/validatingwebhookconfiguration.yaml b/charts/capsule/templates/validatingwebhookconfiguration.yaml index 01e2b327..c20b22a7 100644 --- a/charts/capsule/templates/validatingwebhookconfiguration.yaml +++ b/charts/capsule/templates/validatingwebhookconfiguration.yaml @@ -164,6 +164,7 @@ webhooks: - v1 operations: - CREATE + - UPDATE resources: - pods scope: Namespaced From 7591140fc76f7234dac642e57310e58a47313cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 29 Dec 2022 16:09:51 +0100 Subject: [PATCH 055/153] test(e2e): validate pods on update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- e2e/container_registry_test.go | 144 +++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/e2e/container_registry_test.go b/e2e/container_registry_test.go index 77ff4483..1573f1f8 100644 --- a/e2e/container_registry_test.go +++ b/e2e/container_registry_test.go @@ -111,6 +111,150 @@ var _ = Describe("enforcing a Container Registry", func() { }).Should(Succeed()) }) + It("should deny patching a not matching registry after applying with a matching (Container)", func() { + ns := NewNamespace("reg-deny-container-patch") + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "container", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "myregistry.azurecr.io/myapp:latest", + }, + }, + }, + } + + cs := ownerClient(tnt.Spec.Owners[0]) + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) + + return err + }).Should(Succeed()) + + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: pod.GetName(), Namespace: ns.GetName()}, pod)).ToNot(HaveOccurred()) + + pod.Spec.Containers[0].Image = "attacker/google-containers/pause-amd64:3.0" + Expect(k8sClient.Update(context.Background(), pod)).To(HaveOccurred()) + + Expect(k8sClient.Delete(context.TODO(), ns)).Should(Succeed()) + }) + + It("should deny patching a not matching registry after applying with a matching (initContainer)", func() { + ns := NewNamespace("reg-deny-init-patch") + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "container", + }, + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Name: "init", + Image: "myregistry.azurecr.io/myapp:latest", + }, + }, + Containers: []corev1.Container{ + { + Name: "container", + Image: "myregistry.azurecr.io/myapp:latest", + }, + }, + }, + } + + cs := ownerClient(tnt.Spec.Owners[0]) + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) + + return err + }).Should(Succeed()) + + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: pod.GetName(), Namespace: ns.GetName()}, pod)).ToNot(HaveOccurred()) + + pod.Spec.InitContainers[0].Image = "attacker/google-containers/pause-amd64:3.0" + Expect(k8sClient.Update(context.Background(), pod)).To(HaveOccurred()) + + Expect(k8sClient.Delete(context.TODO(), ns)).Should(Succeed()) + }) + + It("should allow patching a matching registry after applying with a matching (Container)", func() { + ns := NewNamespace("reg-allow-container-patch") + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "container", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "docker.io/google-containers/pause-amd64:3.0", + }, + }, + }, + } + + cs := ownerClient(tnt.Spec.Owners[0]) + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) + + return err + }).Should(Succeed()) + + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: pod.GetName(), Namespace: ns.GetName()}, pod)).ToNot(HaveOccurred()) + + pod.Spec.Containers[0].Image = "myregistry.azurecr.io/google-containers/pause-amd64:3.1" + Expect(k8sClient.Update(context.Background(), pod)).To(HaveOccurred()) + + Expect(k8sClient.Delete(context.TODO(), ns)).Should(Succeed()) + }) + + It("should allow patching a matching registry after applying with a matching (initContainer)", func() { + ns := NewNamespace("reg-allow-container-patch") + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "container", + }, + Spec: corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Name: "init", + Image: "myregistry.azurecr.io/myapp:latest", + }, + }, + Containers: []corev1.Container{ + { + Name: "container", + Image: "docker.io/google-containers/pause-amd64:3.0", + }, + }, + }, + } + + cs := ownerClient(tnt.Spec.Owners[0]) + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) + + return err + }).Should(Succeed()) + + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: pod.GetName(), Namespace: ns.GetName()}, pod)).ToNot(HaveOccurred()) + + pod.Spec.InitContainers[0].Image = "myregistry.azurecr.io/google-containers/pause-amd64:3.1" + Expect(k8sClient.Update(context.Background(), pod)).To(HaveOccurred()) + + Expect(k8sClient.Delete(context.TODO(), ns)).Should(Succeed()) + }) + It("should allow using an exact match", func() { ns := NewNamespace("") NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) From 9f10923d21e6df6b777eb4e420a40a1d12c8e222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 29 Dec 2022 16:11:41 +0100 Subject: [PATCH 056/153] fix: use v1beta2 for capsuleconfiguration kind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- controllers/config/manager.go | 4 ++-- controllers/rbac/manager.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/controllers/config/manager.go b/controllers/config/manager.go index 7be53be8..09f6a0e2 100644 --- a/controllers/config/manager.go +++ b/controllers/config/manager.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/controllers/utils" "github.com/clastix/capsule/pkg/configuration" ) @@ -31,7 +31,7 @@ func (c *Manager) InjectClient(client client.Client) error { func (c *Manager) SetupWithManager(mgr ctrl.Manager, configurationName string) error { return ctrl.NewControllerManagedBy(mgr). - For(&capsulev1alpha1.CapsuleConfiguration{}, utils.NamesMatchingPredicate(configurationName)). + For(&capsulev1beta2.CapsuleConfiguration{}, utils.NamesMatchingPredicate(configurationName)). Complete(c) } diff --git a/controllers/rbac/manager.go b/controllers/rbac/manager.go index 58cf6159..f4470007 100644 --- a/controllers/rbac/manager.go +++ b/controllers/rbac/manager.go @@ -21,7 +21,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/controllers/utils" "github.com/clastix/capsule/pkg/configuration" ) @@ -51,7 +51,7 @@ func (r *Manager) SetupWithManager(ctx context.Context, mgr ctrl.Manager, config crbErr := ctrl.NewControllerManagedBy(mgr). For(&rbacv1.ClusterRoleBinding{}, namesPredicate). - Watches(source.NewKindWithCache(&capsulev1alpha1.CapsuleConfiguration{}, mgr.GetCache()), handler.Funcs{ + Watches(source.NewKindWithCache(&capsulev1beta2.CapsuleConfiguration{}, mgr.GetCache()), handler.Funcs{ UpdateFunc: func(updateEvent event.UpdateEvent, limitingInterface workqueue.RateLimitingInterface) { if updateEvent.ObjectNew.GetName() == configurationName { if crbErr := r.EnsureClusterRoleBindings(ctx); crbErr != nil { From f6c1ad68da095fb75c812b0bfd41a84db79d0297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 29 Dec 2022 16:12:41 +0100 Subject: [PATCH 057/153] chore(makefile): add crds to dev-setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile b/Makefile index fbf209e2..e444eb95 100644 --- a/Makefile +++ b/Makefile @@ -152,8 +152,17 @@ dev-setup: {'op': 'replace', 'path': '/webhooks/6/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/persistentvolumeclaims\",'caBundle':\"$${CA_BUNDLE}\"}},\ {'op': 'replace', 'path': '/webhooks/7/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/services\",'caBundle':\"$${CA_BUNDLE}\"}},\ {'op': 'replace', 'path': '/webhooks/8/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}}\ + ]" && \ + kubectl patch crd tenants.capsule.clastix.io \ + --type='json' -p="[\ + {'op': 'replace', 'path': '/spec/conversion/webhook/clientConfig', 'value':{'url': \"$${WEBHOOK_URL}\", 'caBundle': \"$${CA_BUNDLE}\"}}\ + ]" && \ + kubectl patch crd capsuleconfigurations.capsule.clastix.io \ + --type='json' -p="[\ + {'op': 'replace', 'path': '/spec/conversion/webhook/clientConfig', 'value':{'url': \"$${WEBHOOK_URL}\", 'caBundle': \"$${CA_BUNDLE}\"}}\ ]"; + # Build the docker image docker-build: test docker build . -t ${IMG} --build-arg GIT_HEAD_COMMIT=$(GIT_HEAD_COMMIT) \ From f73a5b17f440932dc198419bec7af02f9503e900 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 29 Dec 2022 16:34:59 +0100 Subject: [PATCH 058/153] fix: using embedded struct for selector --- e2e/pod_priority_class_test.go | 11 ++++++----- e2e/pod_runtime_class_test.go | 10 ++++++---- pkg/api/allowed_list.go | 7 +++---- pkg/api/zz_generated.deepcopy.go | 2 +- pkg/webhook/ingress/errors.go | 2 +- pkg/webhook/pvc/errors.go | 2 +- pkg/webhook/utils/error.go | 2 +- 7 files changed, 19 insertions(+), 17 deletions(-) diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index fff8555c..f2251f79 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -7,16 +7,17 @@ package e2e import ( "context" + "strconv" + "strings" - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" - "github.com/clastix/capsule/pkg/api" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/scheduling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "strconv" - "strings" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a Priority Class", func() { @@ -36,7 +37,7 @@ var _ = Describe("enforcing a Priority Class", func() { Exact: []string{"gold"}, Regex: "pc\\-\\w+", }, - Selector: metav1.LabelSelector{ + LabelSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "env": "customers", }, diff --git a/e2e/pod_runtime_class_test.go b/e2e/pod_runtime_class_test.go index 14d7bd49..e6739da6 100644 --- a/e2e/pod_runtime_class_test.go +++ b/e2e/pod_runtime_class_test.go @@ -7,14 +7,16 @@ package e2e import ( "context" - "github.com/clastix/capsule/pkg/api" + "strconv" + "strings" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" nodev1 "k8s.io/api/node/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "strconv" - "strings" + + "github.com/clastix/capsule/pkg/api" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) @@ -36,7 +38,7 @@ var _ = Describe("enforcing a Runtime Class", func() { Exact: []string{"legacy"}, Regex: "^hardened-.*$", }, - Selector: metav1.LabelSelector{ + LabelSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "env": "customers", }, diff --git a/pkg/api/allowed_list.go b/pkg/api/allowed_list.go index 8d0a9e4f..0784ec6e 100644 --- a/pkg/api/allowed_list.go +++ b/pkg/api/allowed_list.go @@ -16,13 +16,12 @@ import ( // +kubebuilder:object:generate=true type SelectorAllowedListSpec struct { - AllowedListSpec `json:",inline"` - - Selector metav1.LabelSelector `json:",inline"` + AllowedListSpec `json:",inline"` + metav1.LabelSelector `json:",inline"` } func (in *SelectorAllowedListSpec) SelectorMatch(obj client.Object) bool { - selector, err := metav1.LabelSelectorAsSelector(&in.Selector) + selector, err := metav1.LabelSelectorAsSelector(&in.LabelSelector) if err != nil { return false } diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go index d6545982..9419894f 100644 --- a/pkg/api/zz_generated.deepcopy.go +++ b/pkg/api/zz_generated.deepcopy.go @@ -223,7 +223,7 @@ func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec { func (in *SelectorAllowedListSpec) DeepCopyInto(out *SelectorAllowedListSpec) { *out = *in in.AllowedListSpec.DeepCopyInto(&out.AllowedListSpec) - in.Selector.DeepCopyInto(&out.Selector) + in.LabelSelector.DeepCopyInto(&out.LabelSelector) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelectorAllowedListSpec. diff --git a/pkg/webhook/ingress/errors.go b/pkg/webhook/ingress/errors.go index 79f6b5be..c8191c01 100644 --- a/pkg/webhook/ingress/errors.go +++ b/pkg/webhook/ingress/errors.go @@ -77,7 +77,7 @@ func appendClassError(spec api.SelectorAllowedListSpec) (append string) { append += fmt.Sprintf(", or matching the regex %s", spec.Regex) } - if len(spec.Selector.MatchLabels) > 0 || len(spec.Selector.MatchExpressions) > 0 { + if len(spec.MatchLabels) > 0 || len(spec.MatchExpressions) > 0 { append += fmt.Sprintf(", or matching the label selector defined in the Tenant") } diff --git a/pkg/webhook/pvc/errors.go b/pkg/webhook/pvc/errors.go index dc83403e..54660f39 100644 --- a/pkg/webhook/pvc/errors.go +++ b/pkg/webhook/pvc/errors.go @@ -30,7 +30,7 @@ func appendError(spec api.SelectorAllowedListSpec) (append string) { append += fmt.Sprintf(", or matching the regex %s", spec.Regex) } - if len(spec.Selector.MatchLabels) > 0 || len(spec.Selector.MatchExpressions) > 0 { + if len(spec.MatchLabels) > 0 || len(spec.MatchExpressions) > 0 { append += ", or matching the label selector defined in the Tenant" } diff --git a/pkg/webhook/utils/error.go b/pkg/webhook/utils/error.go index c64f21bd..24a34799 100644 --- a/pkg/webhook/utils/error.go +++ b/pkg/webhook/utils/error.go @@ -29,7 +29,7 @@ func AllowedValuesErrorMessage(allowed api.SelectorAllowedListSpec, err string) extra = append(extra, fmt.Sprintf(" use one matching the following regex (%s)", allowed.Regex)) } - if len(allowed.Selector.MatchLabels) > 0 || len(allowed.Selector.MatchExpressions) > 0 { + if len(allowed.MatchLabels) > 0 || len(allowed.MatchExpressions) > 0 { extra = append(extra, ", or matching the label selector defined in the Tenant") } From 51288f30c55d08ec4d43cd2f7e7603eaef61514a Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Mon, 2 Jan 2023 16:18:46 +0100 Subject: [PATCH 059/153] fix(docs): limit range spec for pods and containers limits --- docs/content/general/tutorial.md | 53 +++++++++++++++++--------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/docs/content/general/tutorial.md b/docs/content/general/tutorial.md index 041a47c5..08d28d0a 100644 --- a/docs/content/general/tutorial.md +++ b/docs/content/general/tutorial.md @@ -724,31 +724,34 @@ spec: ... limitRanges: items: - - type: Pod - min: - cpu: "50m" - memory: "5Mi" - max: - cpu: "1" - memory: "1Gi" - - type: Container - defaultRequest: - cpu: "100m" - memory: "10Mi" - default: - cpu: "200m" - memory: "100Mi" - min: - cpu: "50m" - memory: "5Mi" - max: - cpu: "1" - memory: "1Gi" - - type: PersistentVolumeClaim - min: - storage: "1Gi" - max: - storage: "10Gi" + - limits: + - type: Pod + min: + cpu: "50m" + memory: "5Mi" + max: + cpu: "1" + memory: "1Gi" + - limits: + - type: Container + defaultRequest: + cpu: "100m" + memory: "10Mi" + default: + cpu: "200m" + memory: "100Mi" + min: + cpu: "50m" + memory: "5Mi" + max: + cpu: "1" + memory: "1Gi" + - limits: + - type: PersistentVolumeClaim + min: + storage: "1Gi" + max: + storage: "10Gi" ``` Limits will be inherited by all the namespaces created by Alice. In our case, when Alice creates the namespace `oil-production`, Capsule creates the following: From fbea737a51e2dab78a09b6a707ef8b6f5623987f Mon Sep 17 00:00:00 2001 From: Max Fedotov Date: Wed, 11 Jan 2023 16:45:55 +0200 Subject: [PATCH 060/153] feat: add Wargaming to adopters Co-authored-by: Maksim Fedotov --- ADOPTERS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 79f56d11..2e2a22a3 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -6,3 +6,6 @@ This is a list of companies that have adopted Capsule, feel free to open a Pull- ### [Bedag Informatik AG](https://www.bedag.ch/) ![Bedag](https://www.bedag.ch/wGlobal/wGlobal/layout/images/logo.svg) + +### [Wargaming.net](https://www.wargaming.net/) +![Wargaming.net](https://static-cspbe-eu.wargaming.net/images/logo@2x.png) From ab0fe91c58c1c4b2261e5cf4e5513846935f0f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 5 Jan 2023 17:24:38 +0100 Subject: [PATCH 061/153] feat: add defaults handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- api/v1beta2/ingress_options.go | 7 +- api/v1beta2/tenant_conversion_hub.go | 18 ++- api/v1beta2/tenant_types.go | 18 ++- api/v1beta2/zz_generated.deepcopy.go | 6 +- main.go | 4 +- pkg/api/allowed_list.go | 39 ++++- pkg/api/zz_generated.deepcopy.go | 16 ++ pkg/webhook/defaults/errors.go | 56 +++++++ pkg/webhook/defaults/handler.go | 68 ++++++++ pkg/webhook/defaults/ingress.go | 78 ++++++++++ pkg/webhook/defaults/pods.go | 87 +++++++++++ pkg/webhook/defaults/storage.go | 77 ++++++++++ pkg/webhook/ingress/errors.go | 50 +++--- pkg/webhook/ingress/types.go | 41 +++++ pkg/webhook/ingress/utils.go | 4 +- pkg/webhook/ingress/validate_class.go | 179 ++++++---------------- pkg/webhook/ingress/validate_collision.go | 88 ++++------- pkg/webhook/ingress/validate_hostnames.go | 103 +++++-------- pkg/webhook/ingress/validate_wildcard.go | 8 +- pkg/webhook/pod/priorityclass.go | 53 ++++--- pkg/webhook/pod/priorityclass_errors.go | 8 +- pkg/webhook/pod/runtimeclass.go | 17 +- pkg/webhook/pvc/errors.go | 35 ++--- pkg/webhook/pvc/validating.go | 75 ++++----- pkg/webhook/route/defaults.go | 28 ++++ pkg/webhook/utils/error.go | 8 +- pkg/webhook/utils/resources.go | 100 ++++++++++++ pkg/webhook/utils/tenant_by_field.go | 32 ++++ 28 files changed, 890 insertions(+), 413 deletions(-) create mode 100644 pkg/webhook/defaults/errors.go create mode 100644 pkg/webhook/defaults/handler.go create mode 100644 pkg/webhook/defaults/ingress.go create mode 100644 pkg/webhook/defaults/pods.go create mode 100644 pkg/webhook/defaults/storage.go create mode 100644 pkg/webhook/route/defaults.go create mode 100644 pkg/webhook/utils/resources.go create mode 100644 pkg/webhook/utils/tenant_by_field.go diff --git a/api/v1beta2/ingress_options.go b/api/v1beta2/ingress_options.go index 9e821afc..3fe8c4cd 100644 --- a/api/v1beta2/ingress_options.go +++ b/api/v1beta2/ingress_options.go @@ -8,8 +8,11 @@ import ( ) type IngressOptions struct { - // Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. - AllowedClasses *api.SelectorAllowedListSpec `json:"allowedClasses,omitempty"` + // Specifies the allowed IngressClasses assigned to the Tenant. + // Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. + // A default value can be specified, and all the Ingress resources created will inherit the declared class. + // Optional. + AllowedClasses *api.DefaultAllowedListSpec `json:"allowedClasses,omitempty"` // Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. // // diff --git a/api/v1beta2/tenant_conversion_hub.go b/api/v1beta2/tenant_conversion_hub.go index a3dee20a..45c03bb4 100644 --- a/api/v1beta2/tenant_conversion_hub.go +++ b/api/v1beta2/tenant_conversion_hub.go @@ -86,8 +86,10 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { in.Spec.ServiceOptions = src.Spec.ServiceOptions if src.Spec.StorageClasses != nil { - in.Spec.StorageClasses = &api.SelectorAllowedListSpec{ - AllowedListSpec: *src.Spec.StorageClasses, + in.Spec.StorageClasses = &api.DefaultAllowedListSpec{ + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + AllowedListSpec: *src.Spec.StorageClasses, + }, } } @@ -106,8 +108,10 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { } if ingressClass := src.Spec.IngressOptions.AllowedClasses; ingressClass != nil { - in.Spec.IngressOptions.AllowedClasses = &api.SelectorAllowedListSpec{ - AllowedListSpec: *ingressClass, + in.Spec.IngressOptions.AllowedClasses = &api.DefaultAllowedListSpec{ + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + AllowedListSpec: *ingressClass, + }, } } @@ -124,8 +128,10 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { in.Spec.ImagePullPolicies = src.Spec.ImagePullPolicies if src.Spec.PriorityClasses != nil { - in.Spec.PriorityClasses = &api.SelectorAllowedListSpec{ - AllowedListSpec: *src.Spec.PriorityClasses, + in.Spec.PriorityClasses = &api.DefaultAllowedListSpec{ + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + AllowedListSpec: *src.Spec.PriorityClasses, + }, } } diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index 2d9ac06c..c7a18f46 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -17,8 +17,11 @@ type TenantSpec struct { NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"` // Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. ServiceOptions *api.ServiceOptions `json:"serviceOptions,omitempty"` - // Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. - StorageClasses *api.SelectorAllowedListSpec `json:"storageClasses,omitempty"` + // Specifies the allowed StorageClasses assigned to the Tenant. + // Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. + // A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class. + // Optional. + StorageClasses *api.DefaultAllowedListSpec `json:"storageClasses,omitempty"` // Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. IngressOptions IngressOptions `json:"ingressOptions,omitempty"` // Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. @@ -35,10 +38,15 @@ type TenantSpec struct { AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` // Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. ImagePullPolicies []api.ImagePullPolicySpec `json:"imagePullPolicies,omitempty"` - // Specifies the allowed RuntimeClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. Optional. + // Specifies the allowed RuntimeClasses assigned to the Tenant. + // Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. + // Optional. RuntimeClasses *api.SelectorAllowedListSpec `json:"runtimeClasses,omitempty"` - // Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. - PriorityClasses *api.SelectorAllowedListSpec `json:"priorityClasses,omitempty"` + // Specifies the allowed priorityClasses assigned to the Tenant. + // Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. + // A default value can be specified, and all the Pod resources created will inherit the declared class. + // Optional. + PriorityClasses *api.DefaultAllowedListSpec `json:"priorityClasses,omitempty"` // Toggling the Tenant resources cordoning, when enable resources cannot be deleted. Cordoned bool `json:"cordoned,omitempty"` // Prevent accidental deletion of the Tenant. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 2d044ffb..141f2fe9 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -261,7 +261,7 @@ func (in *IngressOptions) DeepCopyInto(out *IngressOptions) { *out = *in if in.AllowedClasses != nil { in, out := &in.AllowedClasses, &out.AllowedClasses - *out = new(api.SelectorAllowedListSpec) + *out = new(api.DefaultAllowedListSpec) (*in).DeepCopyInto(*out) } if in.AllowedHostnames != nil { @@ -718,7 +718,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.StorageClasses != nil { in, out := &in.StorageClasses, &out.StorageClasses - *out = new(api.SelectorAllowedListSpec) + *out = new(api.DefaultAllowedListSpec) (*in).DeepCopyInto(*out) } in.IngressOptions.DeepCopyInto(&out.IngressOptions) @@ -756,7 +756,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } if in.PriorityClasses != nil { in, out := &in.PriorityClasses, &out.PriorityClasses - *out = new(api.SelectorAllowedListSpec) + *out = new(api.DefaultAllowedListSpec) (*in).DeepCopyInto(*out) } } diff --git a/main.go b/main.go index bb901260..323f66a1 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ import ( "github.com/clastix/capsule/pkg/configuration" "github.com/clastix/capsule/pkg/indexer" "github.com/clastix/capsule/pkg/webhook" + "github.com/clastix/capsule/pkg/webhook/defaults" "github.com/clastix/capsule/pkg/webhook/ingress" namespacewebhook "github.com/clastix/capsule/pkg/webhook/namespace" "github.com/clastix/capsule/pkg/webhook/networkpolicy" @@ -237,7 +238,7 @@ func main() { make([]webhook.Webhook, 0), route.Pod(pod.ImagePullPolicy(), pod.ContainerRegistry(), pod.PriorityClass(), pod.RuntimeClass()), route.Namespace(utils.InCapsuleGroups(cfg, namespacewebhook.PatchHandler(), namespacewebhook.QuotaHandler(), namespacewebhook.FreezeHandler(cfg), namespacewebhook.PrefixHandler(cfg), namespacewebhook.UserMetadataHandler())), - route.Ingress(ingress.Class(cfg), ingress.Hostnames(cfg), ingress.Collision(cfg), ingress.Wildcard()), + route.Ingress(ingress.Class(cfg, kubeVersion), ingress.Hostnames(cfg), ingress.Collision(cfg), ingress.Wildcard()), route.PVC(pvc.Handler()), route.Service(service.Handler()), route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())), @@ -245,6 +246,7 @@ func main() { route.OwnerReference(utils.InCapsuleGroups(cfg, namespacewebhook.OwnerReferenceHandler(), ownerreference.Handler(cfg))), route.Cordoning(tenant.CordoningHandler(cfg), tenant.ResourceCounterHandler()), route.Node(utils.InCapsuleGroups(cfg, node.UserMetadataHandler(cfg, kubeVersion))), + route.Defaults(defaults.Handler(cfg, kubeVersion)), ) nodeWebhookSupported, _ := utils.NodeWebhookSupported(kubeVersion) diff --git a/pkg/api/allowed_list.go b/pkg/api/allowed_list.go index 0784ec6e..3d2ef867 100644 --- a/pkg/api/allowed_list.go +++ b/pkg/api/allowed_list.go @@ -15,18 +15,41 @@ import ( // +kubebuilder:object:generate=true +type DefaultAllowedListSpec struct { + SelectorAllowedListSpec `json:",inline"` + Default string `json:"default,omitempty"` +} + +func (in *DefaultAllowedListSpec) MatchDefault(value string) bool { + return in.Default == value +} + +// +kubebuilder:object:generate=true + type SelectorAllowedListSpec struct { AllowedListSpec `json:",inline"` metav1.LabelSelector `json:",inline"` } +func (in *SelectorAllowedListSpec) MatchSelectByName(obj client.Object) bool { + if obj != nil { + return in.AllowedListSpec.Match(obj.GetName()) || in.SelectorMatch(obj) + } + + return false +} + func (in *SelectorAllowedListSpec) SelectorMatch(obj client.Object) bool { - selector, err := metav1.LabelSelectorAsSelector(&in.LabelSelector) - if err != nil { - return false + if obj != nil { + selector, err := metav1.LabelSelectorAsSelector(&in.LabelSelector) + if err != nil { + return false + } + + return selector.Matches(labels.Set(obj.GetLabels())) } - return selector.Matches(labels.Set(obj.GetLabels())) + return false } // +kubebuilder:object:generate=true @@ -36,6 +59,14 @@ type AllowedListSpec struct { Regex string `json:"allowedRegex,omitempty"` } +func (in *AllowedListSpec) Match(value string) (ok bool) { + if in.ExactMatch(value) || in.RegexMatch(value) { + return true + } + + return false +} + func (in *AllowedListSpec) ExactMatch(value string) (ok bool) { if len(in.Exact) > 0 { sort.SliceStable(in.Exact, func(i, j int) bool { diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go index 9419894f..93b61c23 100644 --- a/pkg/api/zz_generated.deepcopy.go +++ b/pkg/api/zz_generated.deepcopy.go @@ -113,6 +113,22 @@ func (in *AllowedServices) DeepCopy() *AllowedServices { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DefaultAllowedListSpec) DeepCopyInto(out *DefaultAllowedListSpec) { + *out = *in + in.SelectorAllowedListSpec.DeepCopyInto(&out.SelectorAllowedListSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultAllowedListSpec. +func (in *DefaultAllowedListSpec) DeepCopy() *DefaultAllowedListSpec { + if in == nil { + return nil + } + out := new(DefaultAllowedListSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalServiceIPsSpec) DeepCopyInto(out *ExternalServiceIPsSpec) { *out = *in diff --git a/pkg/webhook/defaults/errors.go b/pkg/webhook/defaults/errors.go new file mode 100644 index 00000000..85202fb2 --- /dev/null +++ b/pkg/webhook/defaults/errors.go @@ -0,0 +1,56 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package defaults + +import ( + "fmt" +) + +type StorageClassError struct { + storageClass string + msg error +} + +func NewStorageClassError(class string, msg error) error { + return &StorageClassError{ + storageClass: class, + msg: msg, + } +} + +func (e StorageClassError) Error() string { + return fmt.Sprintf("Failed to resolve Storage Class %s: %s", e.storageClass, e.msg) +} + +type IngressClassError struct { + ingressClass string + msg error +} + +func NewIngressClassError(class string, msg error) error { + return &IngressClassError{ + ingressClass: class, + msg: msg, + } +} + +func (e IngressClassError) Error() string { + return fmt.Sprintf("Failed to resolve Ingress Class %s: %s", e.ingressClass, e.msg) +} + +type PriorityClassError struct { + priorityClass string + msg error +} + +func NewPriorityClassError(class string, msg error) error { + return &PriorityClassError{ + priorityClass: class, + msg: msg, + } +} + +func (e PriorityClassError) Error() string { + return fmt.Sprintf("Failed to resolve Priority Class %s: %s", e.priorityClass, e.msg) +} diff --git a/pkg/webhook/defaults/handler.go b/pkg/webhook/defaults/handler.go new file mode 100644 index 00000000..5a3a4f1e --- /dev/null +++ b/pkg/webhook/defaults/handler.go @@ -0,0 +1,68 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package defaults + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/version" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/clastix/capsule/pkg/configuration" + capsulewebhook "github.com/clastix/capsule/pkg/webhook" +) + +type handler struct { + cfg configuration.Configuration + version *version.Version +} + +func Handler(cfg configuration.Configuration, version *version.Version) capsulewebhook.Handler { + return &handler{ + cfg: cfg, + version: version, + } +} + +func (h *handler) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return h.mutate(ctx, req, client, decoder, recorder) + } +} + +func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return nil + } +} + +func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return h.mutate(ctx, req, client, decoder, recorder) + } +} + +func (h *handler) mutate(ctx context.Context, req admission.Request, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response { + var response *admission.Response + + switch { + case req.Resource == (metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}): + response = mutatePodDefaults(ctx, req, c, decoder, recorder) + case req.Resource == (metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumeclaims"}): + response = mutatePVCDefaults(ctx, req, c, decoder, recorder) + case req.Resource == (metav1.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "ingresses"}) || req.Resource == (metav1.GroupVersionResource{Group: "networking.k8s.io", Version: "v1beta1", Resource: "ingresses"}): + response = mutateIngressDefaults(ctx, req, h.version, c, decoder, recorder) + } + + if response == nil { + skip := admission.Allowed("Skipping Mutation") + + response = &skip + } + + return response +} diff --git a/pkg/webhook/defaults/ingress.go b/pkg/webhook/defaults/ingress.go new file mode 100644 index 00000000..8ec687cd --- /dev/null +++ b/pkg/webhook/defaults/ingress.go @@ -0,0 +1,78 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package defaults + +import ( + "context" + "encoding/json" + "net/http" + + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/version" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + capsuleingress "github.com/clastix/capsule/pkg/webhook/ingress" + "github.com/clastix/capsule/pkg/webhook/utils" +) + +func mutateIngressDefaults(ctx context.Context, req admission.Request, version *version.Version, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response { + ingress, err := capsuleingress.FromRequest(req, decoder) + if err != nil { + return utils.ErroredResponse(err) + } + + var tnt *capsulev1beta2.Tenant + + tnt, err = capsuleingress.TenantFromIngress(ctx, c, ingress) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + // Validate Default Ingress + allowed := tnt.Spec.IngressOptions.AllowedClasses + + if allowed == nil || allowed.Default == "" { + return nil + } + + var mutate bool + + var ingressClass client.Object + + if ingressClassName := ingress.IngressClass(); ingressClassName != nil && *ingressClassName != allowed.Default { + if ingressClass, err = utils.GetIngressClassByName(ctx, version, c, ingressClassName); err != nil && !k8serrors.IsNotFound(err) { + response := admission.Denied(NewIngressClassError(*ingressClassName, err).Error()) + + return &response + } + } else { + mutate = true + } + + if mutate = mutate || (utils.IsDefaultIngressClass(ingressClass) && ingressClass.GetName() != allowed.Default); !mutate { + return nil + } + + ingress.SetIngressClass(allowed.Default) + // Marshal Manifest + marshaled, err := json.Marshal(ingress) + if err != nil { + response := admission.Errored(http.StatusInternalServerError, err) + + return &response + } + + recorder.Eventf(tnt, corev1.EventTypeNormal, "TenantDefault", "Assigned Tenant default Ingress Class %s to %s/%s", allowed.Default, ingress.Name(), ingress.Namespace()) + + response := admission.PatchResponseFromRaw(req.Object.Raw, marshaled) + + return &response +} diff --git a/pkg/webhook/defaults/pods.go b/pkg/webhook/defaults/pods.go new file mode 100644 index 00000000..7b1036f7 --- /dev/null +++ b/pkg/webhook/defaults/pods.go @@ -0,0 +1,87 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package defaults + +import ( + "context" + "encoding/json" + "fmt" + + corev1 "k8s.io/api/core/v1" + schedulev1 "k8s.io/api/scheduling/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/webhook/utils" +) + +func mutatePodDefaults(ctx context.Context, req admission.Request, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response { + var err error + + pod := &corev1.Pod{} + if err = decoder.Decode(req, pod); err != nil { + return utils.ErroredResponse(err) + } + + var tnt *capsulev1beta2.Tenant + + tnt, err = utils.TenantByStatusNamespace(ctx, c, pod.Namespace) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + allowed := tnt.Spec.PriorityClasses + + if allowed == nil || allowed.Default == "" { + return nil + } + + priorityClassPod := pod.Spec.PriorityClassName + + var mutate bool + + var cpc *schedulev1.PriorityClass + // PriorityClass name is empty, if no GlobalDefault is set and no PriorityClass was given on pod + if len(priorityClassPod) > 0 && priorityClassPod != allowed.Default { + cpc, err = utils.GetPriorityClassByName(ctx, c, priorityClassPod) + // Should not happen, since API already checks if PC present + if err != nil { + response := admission.Denied(NewPriorityClassError(priorityClassPod, err).Error()) + + return &response + } + } else { + mutate = true + } + + if mutate = mutate || (utils.IsDefaultPriorityClass(cpc) && cpc.GetName() != allowed.Default); !mutate { + return nil + } + + pc, err := utils.GetPriorityClassByName(ctx, c, allowed.Default) + if err != nil { + return utils.ErroredResponse(fmt.Errorf("failed to assign tenant default Priority Class: %w", err)) + } + + pod.Spec.PreemptionPolicy = pc.PreemptionPolicy + pod.Spec.Priority = &pc.Value + pod.Spec.PriorityClassName = pc.Name + // Marshal Pod + marshaled, err := json.Marshal(pod) + if err != nil { + return utils.ErroredResponse(err) + } + + recorder.Eventf(tnt, corev1.EventTypeNormal, "TenantDefault", "Assigned Tenant default Priority Class %s to %s/%s", allowed.Default, pod.Namespace, pod.Name) + + response := admission.PatchResponseFromRaw(req.Object.Raw, marshaled) + + return &response +} diff --git a/pkg/webhook/defaults/storage.go b/pkg/webhook/defaults/storage.go new file mode 100644 index 00000000..e6c12041 --- /dev/null +++ b/pkg/webhook/defaults/storage.go @@ -0,0 +1,77 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package defaults + +import ( + "context" + "encoding/json" + + corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/webhook/utils" +) + +func mutatePVCDefaults(ctx context.Context, req admission.Request, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response { + var err error + + pvc := &corev1.PersistentVolumeClaim{} + if err = decoder.Decode(req, pvc); err != nil { + return utils.ErroredResponse(err) + } + + var tnt *capsulev1beta2.Tenant + + tnt, err = utils.TenantByStatusNamespace(ctx, c, pvc.Namespace) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + allowed := tnt.Spec.StorageClasses + + if allowed == nil || allowed.Default == "" { + return nil + } + + var mutate bool + + var csc *storagev1.StorageClass + + if storageClassName := pvc.Spec.StorageClassName; storageClassName != nil && *storageClassName != allowed.Default { + csc, err = utils.GetStorageClassByName(ctx, c, *storageClassName) + if err != nil && !k8serrors.IsNotFound(err) { + response := admission.Denied(NewStorageClassError(*storageClassName, err).Error()) + + return &response + } + } else { + mutate = true + } + + if mutate = mutate || (utils.IsDefaultStorageClass(csc) && csc.GetName() != allowed.Default); !mutate { + return nil + } + + pvc.Spec.StorageClassName = &tnt.Spec.StorageClasses.Default + // Marshal Manifest + marshaled, err := json.Marshal(pvc) + if err != nil { + return utils.ErroredResponse(err) + } + + recorder.Eventf(tnt, corev1.EventTypeNormal, "TenantDefault", "Assigned Tenant default Storage Class %s to %s/%s", allowed.Default, pvc.Namespace, pvc.Name) + + response := admission.PatchResponseFromRaw(req.Object.Raw, marshaled) + + return &response +} diff --git a/pkg/webhook/ingress/errors.go b/pkg/webhook/ingress/errors.go index c8191c01..040a0097 100644 --- a/pkg/webhook/ingress/errors.go +++ b/pkg/webhook/ingress/errors.go @@ -8,22 +8,25 @@ import ( "strings" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/webhook/utils" ) type ingressClassForbiddenError struct { - className string - spec api.SelectorAllowedListSpec + ingressClassName string + spec api.DefaultAllowedListSpec } -func NewIngressClassForbidden(className string, spec api.SelectorAllowedListSpec) error { +func NewIngressClassForbidden(class string, spec api.DefaultAllowedListSpec) error { return &ingressClassForbiddenError{ - className: className, - spec: spec, + ingressClassName: class, + spec: spec, } } func (i ingressClassForbiddenError) Error() string { - return fmt.Sprintf("Ingress Class %s is forbidden for the current Tenant%s", i.className, appendClassError(i.spec)) + err := fmt.Sprintf("Ingress Class %s is forbidden for the current Tenant: ", i.ingressClassName) + + return utils.DefaultAllowedValuesErrorMessage(i.spec, err) } type ingressHostnameNotValidError struct { @@ -53,35 +56,36 @@ func (i ingressHostnameNotValidError) Error() string { i.invalidHostnames, i.notMatchingHostnames, appendHostnameError(i.spec)) } -type ingressClassNotValidError struct { - spec api.SelectorAllowedListSpec +type ingressClassUndefinedError struct { + spec api.DefaultAllowedListSpec } -func NewIngressClassNotValid(spec api.SelectorAllowedListSpec) error { - return &ingressClassNotValidError{ +func NewIngressClassUndefined(spec api.DefaultAllowedListSpec) error { + return &ingressClassUndefinedError{ spec: spec, } } -func (i ingressClassNotValidError) Error() string { - return "A valid Ingress Class must be used" + appendClassError(i.spec) +func (i ingressClassUndefinedError) Error() string { + return utils.DefaultAllowedValuesErrorMessage(i.spec, "No Ingress Class is forbidden for the current Tenant. Specify a Ingress Class which is allowed within the Tenant: ") } -// nolint:predeclared -func appendClassError(spec api.SelectorAllowedListSpec) (append string) { - if len(spec.Exact) > 0 { - append += fmt.Sprintf(", one of the following (%s)", strings.Join(spec.Exact, ", ")) - } +type ingressClassNotValidError struct { + ingressClassName string + spec api.DefaultAllowedListSpec +} - if len(spec.Regex) > 0 { - append += fmt.Sprintf(", or matching the regex %s", spec.Regex) +func NewIngressClassNotValid(class string, spec api.DefaultAllowedListSpec) error { + return &ingressClassNotValidError{ + ingressClassName: class, + spec: spec, } +} - if len(spec.MatchLabels) > 0 || len(spec.MatchExpressions) > 0 { - append += fmt.Sprintf(", or matching the label selector defined in the Tenant") - } +func (i ingressClassNotValidError) Error() string { + err := fmt.Sprintf("Ingress Class %s is forbidden for the current Tenant: ", i.ingressClassName) - return + return utils.DefaultAllowedValuesErrorMessage(i.spec, err) } // nolint:predeclared diff --git a/pkg/webhook/ingress/types.go b/pkg/webhook/ingress/types.go index 99bab5c3..717eeb45 100644 --- a/pkg/webhook/ingress/types.go +++ b/pkg/webhook/ingress/types.go @@ -21,6 +21,7 @@ type Ingress interface { Namespace() string Name() string HostnamePathsPairs() map[string]sets.String + SetIngressClass(string) } type NetworkingV1 struct { @@ -44,6 +45,20 @@ func (n NetworkingV1) IngressClass() (res *string) { return } +func (n NetworkingV1) SetIngressClass(ingressClassName string) { + if n.Spec.IngressClassName == nil { + if a := n.GetAnnotations(); a != nil { + if _, ok := a[annotationName]; ok { + a[annotationName] = ingressClassName + + return + } + } + } + // Assign in case the IngressClassName property was not set + n.Spec.IngressClassName = &ingressClassName +} + func (n NetworkingV1) Namespace() string { return n.GetNamespace() } @@ -96,6 +111,20 @@ func (n NetworkingV1Beta1) IngressClass() (res *string) { return } +func (n NetworkingV1Beta1) SetIngressClass(ingressClassName string) { + if n.Spec.IngressClassName == nil { + if a := n.GetAnnotations(); a != nil { + if _, ok := a[annotationName]; ok { + a[annotationName] = ingressClassName + + return + } + } + } + // Assign in case the IngressClassName property was not set + n.Annotations[annotationName] = ingressClassName +} + func (n NetworkingV1Beta1) Namespace() string { return n.GetNamespace() } @@ -148,6 +177,18 @@ func (e Extension) IngressClass() (res *string) { return } +func (e Extension) SetIngressClass(ingressClassName string) { + if a := e.GetAnnotations(); a != nil { + if _, ok := a[annotationName]; ok { + a[annotationName] = ingressClassName + + return + } + } + // Assign in case the IngressClassName property was not set + e.Annotations[annotationName] = ingressClassName +} + func (e Extension) Namespace() string { return e.GetNamespace() } diff --git a/pkg/webhook/ingress/utils.go b/pkg/webhook/ingress/utils.go index c9185b93..80ad08b4 100644 --- a/pkg/webhook/ingress/utils.go +++ b/pkg/webhook/ingress/utils.go @@ -17,7 +17,7 @@ import ( capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) -func tenantFromIngress(ctx context.Context, c client.Client, ingress Ingress) (*capsulev1beta2.Tenant, error) { +func TenantFromIngress(ctx context.Context, c client.Client, ingress Ingress) (*capsulev1beta2.Tenant, error) { tenantList := &capsulev1beta2.TenantList{} if err := c.List(ctx, tenantList, client.MatchingFieldsSelector{ Selector: fields.OneTermEqualSelector(".status.namespaces", ingress.Namespace()), @@ -33,7 +33,7 @@ func tenantFromIngress(ctx context.Context, c client.Client, ingress Ingress) (* } // nolint:nakedret -func ingressFromRequest(req admission.Request, decoder *admission.Decoder) (ingress Ingress, err error) { +func FromRequest(req admission.Request, decoder *admission.Decoder) (ingress Ingress, err error) { switch req.Kind.Group { case "networking.k8s.io": if req.Kind.Version == "v1" { diff --git a/pkg/webhook/ingress/validate_class.go b/pkg/webhook/ingress/validate_class.go index 93021072..9f2018d2 100644 --- a/pkg/webhook/ingress/validate_class.go +++ b/pkg/webhook/ingress/validate_class.go @@ -7,12 +7,8 @@ import ( "context" "net/http" - "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - networkingv1beta1 "k8s.io/api/networking/v1beta1" k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/version" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,170 +25,91 @@ type class struct { version *version.Version } -func Class(configuration configuration.Configuration) capsulewebhook.Handler { - version, _ := utils.GetK8sVersion() - +func Class(configuration configuration.Configuration, version *version.Version) capsulewebhook.Handler { return &class{ configuration: configuration, version: version, } } -func (r *class) retrieveIngressClass(ctx context.Context, ctrlClient client.Client, ingressClassName *string) (client.Object, error) { - if r.version == nil || ingressClassName == nil { - return nil, nil - } - - var obj client.Object - - switch { - case r.version.Minor() < 18: - return nil, nil - case r.version.Minor() < 19: - obj = &networkingv1beta1.IngressClass{} - default: - obj = &networkingv1.IngressClass{} +func (r *class) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return r.validate(ctx, r.version, client, req, decoder, recorder) } +} - if err := ctrlClient.Get(ctx, types.NamespacedName{Name: *ingressClassName}, obj); err != nil { - if k8serrors.IsNotFound(err) { - return nil, nil - } - - return nil, err +func (r *class) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return r.validate(ctx, r.version, client, req, decoder, recorder) } - - return obj, nil } -// nolint:dupl -func (r *class) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { +func (r *class) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - ingress, err := ingressFromRequest(req, decoder) - if err != nil { - return utils.ErroredResponse(err) - } - - var tenant *capsulev1beta2.Tenant - - tenant, err = tenantFromIngress(ctx, client, ingress) - if err != nil { - return utils.ErroredResponse(err) - } + return nil + } +} - if tenant == nil { - return nil - } +func (r *class) validate(ctx context.Context, version *version.Version, client client.Client, req admission.Request, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response { + ingress, err := FromRequest(req, decoder) + if err != nil { + return utils.ErroredResponse(err) + } - ic, err := r.retrieveIngressClass(ctx, client, ingress.IngressClass()) - if err != nil { - response := admission.Errored(http.StatusInternalServerError, err) + var tnt *capsulev1beta2.Tenant - return &response - } + tnt, err = TenantFromIngress(ctx, client, ingress) + if err != nil { + return utils.ErroredResponse(err) + } - if err = r.validateClass(*tenant, ingress.IngressClass(), ic); err == nil { - return nil - } + if tnt == nil { + return nil + } - var forbiddenErr *ingressClassForbiddenError + allowed := tnt.Spec.IngressOptions.AllowedClasses - if errors.As(err, &forbiddenErr) { - recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressClassForbidden", "Ingress %s/%s class is forbidden", ingress.Namespace(), ingress.Name()) - } + if allowed == nil { + return nil + } - var invalidErr *ingressClassNotValidError + ingressClass := ingress.IngressClass() - if errors.As(err, &invalidErr) { - recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressClassNotValid", "Ingress %s/%s class is invalid", ingress.Namespace(), ingress.Name()) - } + if ingressClass == nil { + recorder.Eventf(tnt, corev1.EventTypeWarning, "MissingIngressClass", "Ingress %s/%s is missing IngressClass", req.Namespace, req.Name) - response := admission.Denied(err.Error()) + response := admission.Denied(NewIngressClassUndefined(*allowed).Error()) return &response } -} - -// nolint:dupl -func (r *class) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - ingress, err := ingressFromRequest(req, decoder) - if err != nil { - return utils.ErroredResponse(err) - } - var tenant *capsulev1beta2.Tenant + selector := false - tenant, err = tenantFromIngress(ctx, client, ingress) - if err != nil { - return utils.ErroredResponse(err) - } - - if tenant == nil { - return nil - } - - ic, err := r.retrieveIngressClass(ctx, client, ingress.IngressClass()) - if err != nil { + // Verify if the IngressClass exists and matches the label selector/expression + if len(allowed.MatchExpressions) > 0 || len(allowed.MatchLabels) > 0 { + ingressClassObj, err := utils.GetIngressClassByName(ctx, version, client, ingressClass) + if err != nil && !k8serrors.IsNotFound(err) { response := admission.Errored(http.StatusInternalServerError, err) return &response } - if err = r.validateClass(*tenant, ingress.IngressClass(), ic); err == nil { - return nil + // Ingress Class is present, check if it matches the selector + if ingressClassObj != nil { + selector = allowed.SelectorMatch(ingressClassObj) } - - var forbiddenErr *ingressClassForbiddenError - - if errors.As(err, &forbiddenErr) { - recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressClassForbidden", "Ingress %s/%s class is forbidden", ingress.Namespace(), ingress.Name()) - } - - var invalidErr *ingressClassNotValidError - - if errors.As(err, &invalidErr) { - recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressClassNotValid", "Ingress %s/%s class is invalid", ingress.Namespace(), ingress.Name()) - } - - response := admission.Denied(err.Error()) - - return &response } -} -func (r *class) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { + switch { + case allowed.MatchDefault(*ingressClass): return nil - } -} - -func (r *class) validateClass(tenant capsulev1beta2.Tenant, ingressClass *string, ingressClassObj client.Object) error { - if tenant.Spec.IngressOptions.AllowedClasses == nil { + case allowed.Match(*ingressClass) || selector: return nil - } - - if ingressClass == nil { - return NewIngressClassNotValid(*tenant.Spec.IngressOptions.AllowedClasses) - } - - var valid, regex, match bool - - if len(tenant.Spec.IngressOptions.AllowedClasses.Exact) > 0 { - valid = tenant.Spec.IngressOptions.AllowedClasses.ExactMatch(*ingressClass) - } - - regex = tenant.Spec.IngressOptions.AllowedClasses.RegexMatch(*ingressClass) + default: + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenIngressClass", "Ingress %s/%s IngressClass %s is forbidden for the current Tenant", req.Namespace, req.Name, &ingressClass) - if ingressClassObj != nil { - match = tenant.Spec.IngressOptions.AllowedClasses.SelectorMatch(ingressClassObj) - } else { - match = true - } + response := admission.Denied(NewIngressClassForbidden(*ingressClass, *allowed).Error()) - if !valid && !regex && !match { - return NewIngressClassForbidden(*ingressClass, *tenant.Spec.IngressOptions.AllowedClasses) + return &response } - - return nil } diff --git a/pkg/webhook/ingress/validate_collision.go b/pkg/webhook/ingress/validate_collision.go index dd9a73b6..8f286a35 100644 --- a/pkg/webhook/ingress/validate_collision.go +++ b/pkg/webhook/ingress/validate_collision.go @@ -34,80 +34,54 @@ func Collision(configuration configuration.Configuration) capsulewebhook.Handler return &collision{configuration: configuration} } -// nolint:dupl func (r *collision) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - ing, err := ingressFromRequest(req, decoder) - if err != nil { - return utils.ErroredResponse(err) - } - - var tenant *capsulev1beta2.Tenant - - tenant, err = tenantFromIngress(ctx, client, ing) - if err != nil { - return utils.ErroredResponse(err) - } - - if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == api.HostnameCollisionScopeDisabled { - return nil - } - - if err = r.validateCollision(ctx, client, ing, tenant.Spec.IngressOptions.HostnameCollisionScope); err == nil { - return nil - } - - var collisionErr *ingressHostnameCollisionError - - if errors.As(err, &collisionErr) { - recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameCollision", "Ingress %s/%s hostname is colliding", ing.Namespace(), ing.Name()) - } - - response := admission.Denied(err.Error()) - - return &response + return r.validate(ctx, client, req, decoder, recorder) } } -// nolint:dupl func (r *collision) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - ing, err := ingressFromRequest(req, decoder) - if err != nil { - return utils.ErroredResponse(err) - } + return r.validate(ctx, client, req, decoder, recorder) + } +} - var tenant *capsulev1beta2.Tenant +func (r *collision) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return nil + } +} - tenant, err = tenantFromIngress(ctx, client, ing) - if err != nil { - return utils.ErroredResponse(err) - } +func (r *collision) validate(ctx context.Context, client client.Client, req admission.Request, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response { + ing, err := FromRequest(req, decoder) + if err != nil { + return utils.ErroredResponse(err) + } - if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == api.HostnameCollisionScopeDisabled { - return nil - } + var tenant *capsulev1beta2.Tenant - if err = r.validateCollision(ctx, client, ing, tenant.Spec.IngressOptions.HostnameCollisionScope); err == nil { - return nil - } + tenant, err = TenantFromIngress(ctx, client, ing) + if err != nil { + return utils.ErroredResponse(err) + } - var collisionErr *ingressHostnameCollisionError + if tenant == nil || tenant.Spec.IngressOptions.HostnameCollisionScope == api.HostnameCollisionScopeDisabled { + return nil + } - if errors.As(err, &collisionErr) { - recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameCollision", "Ingress %s/%s hostname is colliding", ing.Namespace(), ing.Name()) - } + if err = r.validateCollision(ctx, client, ing, tenant.Spec.IngressOptions.HostnameCollisionScope); err == nil { + return nil + } - response := admission.Denied(err.Error()) + var collisionErr *ingressHostnameCollisionError - return &response + if errors.As(err, &collisionErr) { + recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameCollision", "Ingress %s/%s hostname is colliding", ing.Namespace(), ing.Name()) } -} -func (r *collision) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - return nil - } + response := admission.Denied(err.Error()) + + return &response } // nolint:gocognit,gocyclo,cyclop diff --git a/pkg/webhook/ingress/validate_hostnames.go b/pkg/webhook/ingress/validate_hostnames.go index 84c69af5..c854953e 100644 --- a/pkg/webhook/ingress/validate_hostnames.go +++ b/pkg/webhook/ingress/validate_hostnames.go @@ -30,90 +30,59 @@ func Hostnames(configuration configuration.Configuration) capsulewebhook.Handler func (r *hostnames) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - ingress, err := ingressFromRequest(req, decoder) - if err != nil { - return utils.ErroredResponse(err) - } - - var tenant *capsulev1beta2.Tenant - - tenant, err = tenantFromIngress(ctx, c, ingress) - if err != nil { - return utils.ErroredResponse(err) - } - - if tenant == nil || tenant.Spec.IngressOptions.AllowedHostnames == nil { - return nil - } - - hostnameList := sets.NewString() - for hostname := range ingress.HostnamePathsPairs() { - hostnameList.Insert(hostname) - } - - if err = r.validateHostnames(*tenant, hostnameList); err == nil { - return nil - } - - var hostnameNotValidErr *ingressHostnameNotValidError - - if errors.As(err, &hostnameNotValidErr) { - recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameNotValid", "Ingress %s/%s hostname is not valid", ingress.Namespace(), ingress.Name()) - - response := admission.Denied(err.Error()) - - return &response - } - - return utils.ErroredResponse(err) + return r.validate(ctx, c, req, decoder, recorder) } } func (r *hostnames) OnUpdate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - ingress, err := ingressFromRequest(req, decoder) - if err != nil { - return utils.ErroredResponse(err) - } + return r.validate(ctx, c, req, decoder, recorder) + } +} + +func (r *hostnames) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return nil + } +} - var tenant *capsulev1beta2.Tenant +func (r *hostnames) validate(ctx context.Context, client client.Client, req admission.Request, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response { + ingress, err := FromRequest(req, decoder) + if err != nil { + return utils.ErroredResponse(err) + } - tenant, err = tenantFromIngress(ctx, c, ingress) - if err != nil { - return utils.ErroredResponse(err) - } + var tenant *capsulev1beta2.Tenant - if tenant == nil { - return nil - } + tenant, err = TenantFromIngress(ctx, client, ingress) + if err != nil { + return utils.ErroredResponse(err) + } - hostnameSet := sets.NewString() - for hostname := range ingress.HostnamePathsPairs() { - hostnameSet.Insert(hostname) - } + if tenant == nil || tenant.Spec.IngressOptions.AllowedHostnames == nil { + return nil + } - if err = r.validateHostnames(*tenant, hostnameSet); err == nil { - return nil - } + hostnameList := sets.NewString() + for hostname := range ingress.HostnamePathsPairs() { + hostnameList.Insert(hostname) + } - var hostnameNotValidErr *ingressHostnameNotValidError + if err = r.validateHostnames(*tenant, hostnameList); err == nil { + return nil + } - if errors.As(err, &hostnameNotValidErr) { - recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameNotValid", "Ingress %s/%s hostname is not valid", ingress.Namespace(), ingress.Name()) + var hostnameNotValidErr *ingressHostnameNotValidError - response := admission.Denied(err.Error()) + if errors.As(err, &hostnameNotValidErr) { + recorder.Eventf(tenant, corev1.EventTypeWarning, "IngressHostnameNotValid", "Ingress %s/%s hostname is not valid", ingress.Namespace(), ingress.Name()) - return &response - } + response := admission.Denied(err.Error()) - return utils.ErroredResponse(err) + return &response } -} -func (r *hostnames) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - return nil - } + return utils.ErroredResponse(err) } func (r *hostnames) validateHostnames(tenant capsulev1beta2.Tenant, hostnames sets.String) error { diff --git a/pkg/webhook/ingress/validate_wildcard.go b/pkg/webhook/ingress/validate_wildcard.go index 1cc9a4a3..c4c8a132 100644 --- a/pkg/webhook/ingress/validate_wildcard.go +++ b/pkg/webhook/ingress/validate_wildcard.go @@ -27,7 +27,7 @@ func Wildcard() capsulewebhook.Handler { func (h *wildcard) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - return h.wildcardHandler(ctx, client, req, recorder, decoder) + return h.validate(ctx, client, req, recorder, decoder) } } @@ -39,11 +39,11 @@ func (h *wildcard) OnDelete(client client.Client, decoder *admission.Decoder, re func (h *wildcard) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - return h.wildcardHandler(ctx, client, req, recorder, decoder) + return h.validate(ctx, client, req, recorder, decoder) } } -func (h *wildcard) wildcardHandler(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder, decoder *admission.Decoder) *admission.Response { +func (h *wildcard) validate(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder, decoder *admission.Decoder) *admission.Response { tntList := &capsulev1beta2.TenantList{} if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{ @@ -61,7 +61,7 @@ func (h *wildcard) wildcardHandler(ctx context.Context, clt client.Client, req a if !tnt.Spec.IngressOptions.AllowWildcardHostnames { // Retrieve ingress resource from request. - ingress, err := ingressFromRequest(req, decoder) + ingress, err := FromRequest(req, decoder) if err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/pod/priorityclass.go b/pkg/webhook/pod/priorityclass.go index f431b900..d56ceb95 100644 --- a/pkg/webhook/pod/priorityclass.go +++ b/pkg/webhook/pod/priorityclass.go @@ -10,13 +10,11 @@ import ( corev1 "k8s.io/api/core/v1" schedulingv1 "k8s.io/api/scheduling/v1" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -52,44 +50,57 @@ func (h *priorityClass) OnCreate(c client.Client, decoder *admission.Decoder, re return utils.ErroredResponse(err) } - tntList := &capsulev1beta2.TenantList{} - - if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace), - }); err != nil { + tnt, err := utils.TenantByStatusNamespace(ctx, c, pod.Namespace) + if err != nil { return utils.ErroredResponse(err) } - if len(tntList.Items) == 0 { + if tnt == nil { return nil } - allowed := tntList.Items[0].Spec.PriorityClasses + allowed := tnt.Spec.PriorityClasses + + if allowed == nil { + return nil + } priorityClassName := pod.Spec.PriorityClassName - class, err := h.class(ctx, c, priorityClassName) - if err != nil { - response := admission.Errored(http.StatusInternalServerError, err) + if len(priorityClassName) == 0 { + // We don't have to force Pod to specify a Priority Class + return nil + } - return &response + selector := false + + // Verify if the StorageClass exists and matches the label selector/expression + if len(allowed.MatchExpressions) > 0 || len(allowed.MatchLabels) > 0 { + priorityClassObj, err := utils.GetPriorityClassByName(ctx, c, priorityClassName) + if err != nil { + response := admission.Errored(http.StatusInternalServerError, err) + + return &response + } + + // Storage Class is present, check if it matches the selector + if priorityClassObj != nil { + selector = allowed.SelectorMatch(priorityClassObj) + } } switch { - case allowed == nil: - // Enforcement is not in place, skipping it at all + case allowed.MatchDefault(priorityClassName): + // Allow if given Priority Class is equal tenant default (eventough it's not allowed by selector) return nil - case len(priorityClassName) == 0: - // We don't have to force Pod to specify a Priority Class + case allowed.Match(priorityClassName) || selector: return nil - case !allowed.ExactMatch(priorityClassName) && !allowed.RegexMatch(priorityClassName) && !allowed.SelectorMatch(class): - recorder.Eventf(&tntList.Items[0], corev1.EventTypeWarning, "ForbiddenPriorityClass", "Pod %s/%s is using Priority Class %s is forbidden for the current Tenant", pod.Namespace, pod.Name, priorityClassName) + default: + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenPriorityClass", "Pod %s/%s is using Priority Class %s is forbidden for the current Tenant", pod.Namespace, pod.Name, priorityClassName) response := admission.Denied(NewPodPriorityClassForbidden(priorityClassName, *allowed).Error()) return &response - default: - return nil } } } diff --git a/pkg/webhook/pod/priorityclass_errors.go b/pkg/webhook/pod/priorityclass_errors.go index 64f2cf12..9198d598 100644 --- a/pkg/webhook/pod/priorityclass_errors.go +++ b/pkg/webhook/pod/priorityclass_errors.go @@ -12,10 +12,10 @@ import ( type podPriorityClassForbiddenError struct { priorityClassName string - spec api.SelectorAllowedListSpec + spec api.DefaultAllowedListSpec } -func NewPodPriorityClassForbidden(priorityClassName string, spec api.SelectorAllowedListSpec) error { +func NewPodPriorityClassForbidden(priorityClassName string, spec api.DefaultAllowedListSpec) error { return &podPriorityClassForbiddenError{ priorityClassName: priorityClassName, spec: spec, @@ -23,7 +23,7 @@ func NewPodPriorityClassForbidden(priorityClassName string, spec api.SelectorAll } func (f podPriorityClassForbiddenError) Error() (err string) { - err = fmt.Sprintf("Pod Priorioty Class %s is forbidden for the current Tenant: ", f.priorityClassName) + msg := fmt.Sprintf("Pod Priority Class %s is forbidden for the current Tenant: ", f.priorityClassName) - return utils.AllowedValuesErrorMessage(f.spec, err) + return utils.DefaultAllowedValuesErrorMessage(f.spec, msg) } diff --git a/pkg/webhook/pod/runtimeclass.go b/pkg/webhook/pod/runtimeclass.go index 0de42591..08d539c0 100644 --- a/pkg/webhook/pod/runtimeclass.go +++ b/pkg/webhook/pod/runtimeclass.go @@ -9,13 +9,11 @@ import ( corev1 "k8s.io/api/core/v1" nodev1 "k8s.io/api/node/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -63,19 +61,16 @@ func (h *runtimeClass) validate(ctx context.Context, c client.Client, decoder *a return utils.ErroredResponse(err) } - tntList := &capsulev1beta2.TenantList{} - - if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace), - }); err != nil { + tnt, err := utils.TenantByStatusNamespace(ctx, c, pod.Namespace) + if err != nil { return utils.ErroredResponse(err) } - if len(tntList.Items) == 0 { + if tnt == nil { return nil } - allowed := tntList.Items[0].Spec.RuntimeClasses + allowed := tnt.Spec.RuntimeClasses runtimeClassName := "" if pod.Spec.RuntimeClassName != nil { @@ -96,8 +91,8 @@ func (h *runtimeClass) validate(ctx context.Context, c client.Client, decoder *a case len(runtimeClassName) == 0: // We don't have to force Pod to specify a RuntimeClass return nil - case !allowed.ExactMatch(runtimeClassName) && !allowed.RegexMatch(runtimeClassName) && !allowed.SelectorMatch(class): - recorder.Eventf(&tntList.Items[0], corev1.EventTypeWarning, "ForbiddenRuntimeClass", "Pod %s/%s is using Runtime Class %s is forbidden for the current Tenant", pod.Namespace, pod.Name, runtimeClassName) + case !allowed.MatchSelectByName(class): + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenRuntimeClass", "Pod %s/%s is using Runtime Class %s is forbidden for the current Tenant", pod.Namespace, pod.Name, runtimeClassName) response := admission.Denied(NewPodRuntimeClassForbidden(runtimeClassName, *allowed).Error()) diff --git a/pkg/webhook/pvc/errors.go b/pkg/webhook/pvc/errors.go index 54660f39..f02789ae 100644 --- a/pkg/webhook/pvc/errors.go +++ b/pkg/webhook/pvc/errors.go @@ -5,48 +5,33 @@ package pvc import ( "fmt" - "strings" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/webhook/utils" ) type storageClassNotValidError struct { - spec api.SelectorAllowedListSpec + spec api.DefaultAllowedListSpec } -func NewStorageClassNotValid(storageClasses api.SelectorAllowedListSpec) error { +func NewStorageClassNotValid(storageClasses api.DefaultAllowedListSpec) error { return &storageClassNotValidError{ spec: storageClasses, } } -// nolint:predeclared -func appendError(spec api.SelectorAllowedListSpec) (append string) { - if len(spec.Exact) > 0 { - append += fmt.Sprintf(", one of the following (%s)", strings.Join(spec.Exact, ", ")) - } - - if len(spec.Regex) > 0 { - append += fmt.Sprintf(", or matching the regex %s", spec.Regex) - } - - if len(spec.MatchLabels) > 0 || len(spec.MatchExpressions) > 0 { - append += ", or matching the label selector defined in the Tenant" - } - - return -} - func (s storageClassNotValidError) Error() (err string) { - return "A valid Storage Class must be used" + appendError(s.spec) + msg := "A valid Storage Class must be used: " + + return utils.DefaultAllowedValuesErrorMessage(s.spec, msg) } type storageClassForbiddenError struct { className string - spec api.SelectorAllowedListSpec + spec api.DefaultAllowedListSpec } -func NewStorageClassForbidden(className string, storageClasses api.SelectorAllowedListSpec) error { +func NewStorageClassForbidden(className string, storageClasses api.DefaultAllowedListSpec) error { return &storageClassForbiddenError{ className: className, spec: storageClasses, @@ -54,5 +39,7 @@ func NewStorageClassForbidden(className string, storageClasses api.SelectorAllow } func (f storageClassForbiddenError) Error() string { - return fmt.Sprintf("Storage Class %s is forbidden for the current Tenant%s", f.className, appendError(f.spec)) + msg := fmt.Sprintf("Storage Class %s is forbidden for the current Tenant ", f.className) + + return utils.DefaultAllowedValuesErrorMessage(f.spec, msg) } diff --git a/pkg/webhook/pvc/validating.go b/pkg/webhook/pvc/validating.go index 4014c949..52d2dc70 100644 --- a/pkg/webhook/pvc/validating.go +++ b/pkg/webhook/pvc/validating.go @@ -8,15 +8,11 @@ import ( "net/http" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" capsulewebhook "github.com/clastix/capsule/pkg/webhook" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -27,20 +23,6 @@ func Handler() capsulewebhook.Handler { return &handler{} } -func (h *handler) getStorageClass(ctx context.Context, c client.Client, name string) (client.Object, error) { - obj := &v1.StorageClass{} - - if err := c.Get(ctx, types.NamespacedName{Name: name}, obj); err != nil { - if errors.IsNotFound(err) { - return nil, nil - } - - return nil, err - } - - return obj, nil -} - func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { pvc := &corev1.PersistentVolumeClaim{} @@ -48,59 +30,60 @@ func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder return utils.ErroredResponse(err) } - tntList := &capsulev1beta2.TenantList{} - if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", pvc.Namespace), - }); err != nil { + tnt, err := utils.TenantByStatusNamespace(ctx, c, pvc.Namespace) + if err != nil { return utils.ErroredResponse(err) } - if len(tntList.Items) == 0 { + if tnt == nil { return nil } - tnt := tntList.Items[0] + allowed := tnt.Spec.StorageClasses - if tnt.Spec.StorageClasses == nil { + if allowed == nil { return nil } - if pvc.Spec.StorageClassName == nil { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "MissingStorageClass", "PersistentVolumeClaim %s/%s is missing StorageClass", req.Namespace, req.Name) - - response := admission.Denied(NewStorageClassNotValid(*tntList.Items[0].Spec.StorageClasses).Error()) + storageClass := pvc.Spec.StorageClassName - return &response - } + if storageClass == nil { + recorder.Eventf(tnt, corev1.EventTypeWarning, "MissingStorageClass", "PersistentVolumeClaim %s/%s is missing StorageClass", req.Namespace, req.Name) - sc := *pvc.Spec.StorageClassName - - scObj, err := h.getStorageClass(ctx, c, sc) - if err != nil { - response := admission.Errored(http.StatusInternalServerError, err) + response := admission.Denied(NewStorageClassNotValid(*tnt.Spec.StorageClasses).Error()) return &response } - var valid, regex, match bool + selector := false + + // Verify if the StorageClass exists and matches the label selector/expression + if len(allowed.MatchExpressions) > 0 || len(allowed.MatchLabels) > 0 { + storageClassObj, err := utils.GetStorageClassByName(ctx, c, *storageClass) + if err != nil && !errors.IsNotFound(err) { + response := admission.Errored(http.StatusInternalServerError, err) - valid, regex = tnt.Spec.StorageClasses.ExactMatch(sc), tnt.Spec.StorageClasses.RegexMatch(sc) + return &response + } - if scObj != nil { - match = tnt.Spec.StorageClasses.SelectorMatch(scObj) - } else { - match = true + // Storage Class is present, check if it matches the selector + if storageClassObj != nil { + selector = allowed.SelectorMatch(storageClassObj) + } } - if !valid && !regex && !match { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenStorageClass", "PersistentVolumeClaim %s/%s StorageClass %s is forbidden for the current Tenant", req.Namespace, req.Name, sc) + switch { + case allowed.MatchDefault(*storageClass): + return nil + case allowed.Match(*storageClass) || selector: + return nil + default: + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenStorageClass", "PersistentVolumeClaim %s/%s StorageClass %s is forbidden for the current Tenant", req.Namespace, req.Name, *storageClass) response := admission.Denied(NewStorageClassForbidden(*pvc.Spec.StorageClassName, *tnt.Spec.StorageClasses).Error()) return &response } - - return nil } } diff --git a/pkg/webhook/route/defaults.go b/pkg/webhook/route/defaults.go new file mode 100644 index 00000000..b42ca895 --- /dev/null +++ b/pkg/webhook/route/defaults.go @@ -0,0 +1,28 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package route + +import ( + capsulewebhook "github.com/clastix/capsule/pkg/webhook" +) + +// +kubebuilder:webhook:path=/defaults,mutating=true,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="",resources=pods,verbs=create,versions=v1,name=pod.defaults.capsule.clastix.io +// +kubebuilder:webhook:path=/defaults,mutating=true,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="",resources=persistentvolumeclaims,verbs=create,versions=v1,name=storage.defaults.capsule.clastix.io +// +kubebuilder:webhook:path=/defaults,mutating=true,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups=networking.k8s.io,resources=ingresses,verbs=create;update,versions=v1beta1;v1,name=ingress.defaults.capsule.clastix.io + +type defaults struct { + handlers []capsulewebhook.Handler +} + +func Defaults(handler ...capsulewebhook.Handler) capsulewebhook.Webhook { + return &defaults{handlers: handler} +} + +func (w *defaults) GetHandlers() []capsulewebhook.Handler { + return w.handlers +} + +func (w *defaults) GetPath() string { + return "/defaults" +} diff --git a/pkg/webhook/utils/error.go b/pkg/webhook/utils/error.go index 24a34799..fe137ee9 100644 --- a/pkg/webhook/utils/error.go +++ b/pkg/webhook/utils/error.go @@ -19,6 +19,10 @@ func ErroredResponse(err error) *admission.Response { return &response } +func DefaultAllowedValuesErrorMessage(allowed api.DefaultAllowedListSpec, err string) string { + return AllowedValuesErrorMessage(allowed.SelectorAllowedListSpec, err) +} + func AllowedValuesErrorMessage(allowed api.SelectorAllowedListSpec, err string) string { var extra []string if len(allowed.Exact) > 0 { @@ -26,11 +30,11 @@ func AllowedValuesErrorMessage(allowed api.SelectorAllowedListSpec, err string) } if len(allowed.Regex) > 0 { - extra = append(extra, fmt.Sprintf(" use one matching the following regex (%s)", allowed.Regex)) + extra = append(extra, fmt.Sprintf("use one matching the following regex (%s)", allowed.Regex)) } if len(allowed.MatchLabels) > 0 || len(allowed.MatchExpressions) > 0 { - extra = append(extra, ", or matching the label selector defined in the Tenant") + extra = append(extra, "matching the label selector defined in the Tenant") } err += strings.Join(extra, " or ") diff --git a/pkg/webhook/utils/resources.go b/pkg/webhook/utils/resources.go new file mode 100644 index 00000000..d729a0e9 --- /dev/null +++ b/pkg/webhook/utils/resources.go @@ -0,0 +1,100 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + + networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" + schedulev1 "k8s.io/api/scheduling/v1" + storagev1 "k8s.io/api/storage/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/version" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const TRUE string = "true" + +// Get PriorityClass by name (Does not return error if not found). +func GetPriorityClassByName(ctx context.Context, c client.Client, name string) (*schedulev1.PriorityClass, error) { + class := &schedulev1.PriorityClass{} + if err := c.Get(ctx, types.NamespacedName{Name: name}, class); err != nil { + return nil, err + } + + return class, nil +} + +// Get StorageClass by name (Does not return error if not found). +func GetStorageClassByName(ctx context.Context, c client.Client, name string) (*storagev1.StorageClass, error) { + class := &storagev1.StorageClass{} + if err := c.Get(ctx, types.NamespacedName{Name: name}, class); err != nil { + return nil, err + } + + return class, nil +} + +// Get IngressClass by name (Does not return error if not found). +func GetIngressClassByName(ctx context.Context, version *version.Version, c client.Client, ingressClassName *string) (client.Object, error) { + if ingressClassName == nil { + return nil, nil + } + + var obj client.Object + + switch { + case version == nil: + obj = &networkingv1.IngressClass{} + case version.Minor() < 18: + return nil, nil + case version.Minor() < 19: + obj = &networkingv1beta1.IngressClass{} + default: + obj = &networkingv1.IngressClass{} + } + + if err := c.Get(ctx, types.NamespacedName{Name: *ingressClassName}, obj); err != nil { + return nil, err + } + + return obj, nil +} + +// IsDefaultPriorityClass checks if the given PriorityClass is cluster default. +func IsDefaultPriorityClass(class *schedulev1.PriorityClass) bool { + if class != nil { + return class.GlobalDefault + } + + return false +} + +func IsDefaultIngressClass(class client.Object) bool { + annotation := "ingressclass.kubernetes.io/is-default-class" + + if class != nil { + annotations := class.GetAnnotations() + if v, ok := annotations[annotation]; ok && v == TRUE { + return true + } + } + + return false +} + +// IsDefaultStorageClass checks if the given StorageClass is cluster default. +func IsDefaultStorageClass(class client.Object) bool { + annotation := "storageclass.kubernetes.io/is-default-class" + + if class != nil { + annotations := class.GetAnnotations() + if v, ok := annotations[annotation]; ok && v == TRUE { + return true + } + } + + return false +} diff --git a/pkg/webhook/utils/tenant_by_field.go b/pkg/webhook/utils/tenant_by_field.go new file mode 100644 index 00000000..3c9af276 --- /dev/null +++ b/pkg/webhook/utils/tenant_by_field.go @@ -0,0 +1,32 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + + "k8s.io/apimachinery/pkg/fields" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +func TenantByStatusNamespace(ctx context.Context, c client.Client, namespace string) (*capsulev1beta2.Tenant, error) { + tntList := &capsulev1beta2.TenantList{} + tnt := &capsulev1beta2.Tenant{} + + if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ + Selector: fields.OneTermEqualSelector(".status.namespaces", namespace), + }); err != nil { + return nil, err + } + + if len(tntList.Items) == 0 { + return tnt, nil + } + + *tnt = tntList.Items[0] + + return tnt, nil +} From a1b624f239072c43d8b2e820ca8186aad2d40a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 5 Jan 2023 17:25:14 +0100 Subject: [PATCH 062/153] chore(kustomize): add defaults handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- .../crd/bases/capsule.clastix.io_tenants.yaml | 16 +++- config/install.yaml | 86 ++++++++++++++++++- config/webhook/kustomization.yaml | 9 +- config/webhook/manifests.yaml | 59 +++++++++++++ .../webhook/patch_mutating_ns_selector.yaml | 27 ++++++ ...yaml => patch_validating_ns_selector.yaml} | 0 6 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 config/webhook/patch_mutating_ns_selector.yaml rename config/webhook/{patch_ns_selector.yaml => patch_validating_ns_selector.yaml} (100%) diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index f783ba48..8f3f4bd0 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -2048,7 +2048,8 @@ spec: description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. - Optional. + A default value can be specified, and all the Ingress resources + created will inherit the declared class. Optional. properties: allowed: items: @@ -2056,6 +2057,8 @@ spec: type: array allowedRegex: type: string + default: + type: string matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -2868,7 +2871,9 @@ spec: priorityClasses: description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant - can use only one of the allowed PriorityClasses. Optional. + can use only one of the allowed PriorityClasses. A default value + can be specified, and all the Pod resources created will inherit + the declared class. Optional. properties: allowed: items: @@ -2876,6 +2881,8 @@ spec: type: array allowedRegex: type: string + default: + type: string matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -3110,7 +3117,8 @@ spec: description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. - Optional. + A default value can be specified, and all the PersistentVolumeClaim + resources created will inherit the declared class. Optional. properties: allowed: items: @@ -3118,6 +3126,8 @@ spec: type: array allowedRegex: type: string + default: + type: string matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. diff --git a/config/install.yaml b/config/install.yaml index 8d4dcedd..00e83c9d 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -1940,7 +1940,7 @@ spec: description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. type: boolean allowedClasses: - description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. A default value can be specified, and all the Ingress resources created will inherit the declared class. Optional. properties: allowed: items: @@ -1948,6 +1948,8 @@ spec: type: array allowedRegex: type: string + default: + type: string matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: @@ -2442,7 +2444,7 @@ spec: description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined. type: boolean priorityClasses: - description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. A default value can be specified, and all the Pod resources created will inherit the declared class. Optional. properties: allowed: items: @@ -2450,6 +2452,8 @@ spec: type: array allowedRegex: type: string + default: + type: string matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: @@ -2617,7 +2621,7 @@ spec: type: object type: object storageClasses: - description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class. Optional. properties: allowed: items: @@ -2625,6 +2629,8 @@ spec: type: array allowedRegex: type: string + default: + type: string matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: @@ -2812,6 +2818,80 @@ metadata: creationTimestamp: null name: capsule-mutating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: capsule-webhook-service + namespace: capsule-system + path: /defaults + failurePolicy: Fail + name: pod.defaults.capsule.clastix.io + namespaceSelector: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - pods + scope: Namespaced + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: capsule-webhook-service + namespace: capsule-system + path: /defaults + failurePolicy: Fail + name: storage.defaults.capsule.clastix.io + namespaceSelector: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - persistentvolumeclaims + scope: Namespaced + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: capsule-webhook-service + namespace: capsule-system + path: /defaults + failurePolicy: Fail + name: ingress.defaults.capsule.clastix.io + namespaceSelector: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1beta1 + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + scope: Namespaced + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml index 8b7eb535..8ff7dd79 100644 --- a/config/webhook/kustomization.yaml +++ b/config/webhook/kustomization.yaml @@ -8,7 +8,14 @@ patchesJson6902: kind: ValidatingWebhookConfiguration name: validating-webhook-configuration version: v1 - path: patch_ns_selector.yaml + path: patch_validating_ns_selector.yaml +- target: + group: admissionregistration.k8s.io + kind: MutatingWebhookConfiguration + name: mutating-webhook-configuration + version: v1 + path: patch_mutating_ns_selector.yaml + configurations: - kustomizeconfig.yaml diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index db05648a..f82891d2 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -5,6 +5,65 @@ metadata: creationTimestamp: null name: mutating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /defaults + failurePolicy: Fail + name: pod.defaults.capsule.clastix.io + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - pods + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /defaults + failurePolicy: Fail + name: storage.defaults.capsule.clastix.io + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - persistentvolumeclaims + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /defaults + failurePolicy: Fail + name: ingress.defaults.capsule.clastix.io + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1beta1 + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/config/webhook/patch_mutating_ns_selector.yaml b/config/webhook/patch_mutating_ns_selector.yaml new file mode 100644 index 00000000..790b06e1 --- /dev/null +++ b/config/webhook/patch_mutating_ns_selector.yaml @@ -0,0 +1,27 @@ +- op: add + path: /webhooks/0/namespaceSelector + value: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists +- op: add + path: /webhooks/1/namespaceSelector + value: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists +- op: add + path: /webhooks/2/namespaceSelector + value: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists +- op: add + path: /webhooks/0/rules/0/scope + value: Namespaced +- op: add + path: /webhooks/1/rules/0/scope + value: Namespaced +- op: add + path: /webhooks/2/rules/0/scope + value: Namespaced diff --git a/config/webhook/patch_ns_selector.yaml b/config/webhook/patch_validating_ns_selector.yaml similarity index 100% rename from config/webhook/patch_ns_selector.yaml rename to config/webhook/patch_validating_ns_selector.yaml From 8f933cd2b3598a43fc85ae0305c1901686277fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 5 Jan 2023 17:25:42 +0100 Subject: [PATCH 063/153] chore(helm): add defaults handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- charts/capsule/README.md | 9 + charts/capsule/crds/tenant-crd.yaml | 4932 +++++++---------- .../mutatingwebhookconfiguration.yaml | 80 + .../validatingwebhookconfiguration.yaml | 56 +- charts/capsule/values.yaml | 20 + 5 files changed, 2070 insertions(+), 3027 deletions(-) diff --git a/charts/capsule/README.md b/charts/capsule/README.md index a4772e96..3d34b8d3 100644 --- a/charts/capsule/README.md +++ b/charts/capsule/README.md @@ -130,6 +130,15 @@ Here the values you can override: | webhooks.cordoning.failurePolicy | string | `"Fail"` | | | webhooks.cordoning.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | | | webhooks.cordoning.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | | +| webhooks.defaults.ingress.failurePolicy | string | `"Fail"` | | +| webhooks.defaults.ingress.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | | +| webhooks.defaults.ingress.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | | +| webhooks.defaults.pods.failurePolicy | string | `"Fail"` | | +| webhooks.defaults.pods.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | | +| webhooks.defaults.pods.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | | +| webhooks.defaults.pvc.failurePolicy | string | `"Fail"` | | +| webhooks.defaults.pvc.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | | +| webhooks.defaults.pvc.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | | | webhooks.ingresses.failurePolicy | string | `"Fail"` | | | webhooks.ingresses.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | | | webhooks.ingresses.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | | diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index f783ba48..ae8b2a15 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -4,3190 +4,2124 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null name: tenants.capsule.clastix.io spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: capsule-webhook-service + namespace: capsule-system + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1beta1 + - v1beta2 group: capsule.clastix.io names: kind: Tenant listKind: TenantList plural: tenants shortNames: - - tnt + - tnt singular: tenant scope: Cluster versions: - - additionalPrinterColumns: - - description: The max amount of Namespaces can be created - jsonPath: .spec.namespaceQuota - name: Namespace quota - type: integer - - description: The total amount of Namespaces in use - jsonPath: .status.size - name: Namespace count - type: integer - - description: The assigned Tenant owner - jsonPath: .spec.owner.name - name: Owner name - type: string - - description: The assigned Tenant owner kind - jsonPath: .spec.owner.kind - name: Owner kind - type: string - - description: Node Selector applied to Pods - jsonPath: .spec.nodeSelector - name: Node selector - type: string - - description: Age - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: This version is going to be dropped in the upcoming version - of Capsule; please, migrate to v1beta2 version. - name: v1alpha1 - schema: - openAPIV3Schema: - description: Tenant is the Schema for the tenants API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TenantSpec defines the desired state of Tenant. - properties: - additionalRoleBindings: - items: + - additionalPrinterColumns: + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceQuota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: The assigned Tenant owner + jsonPath: .spec.owner.name + name: Owner name + type: string + - description: The assigned Tenant owner kind + jsonPath: .spec.owner.kind + name: Owner kind + type: string + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version. + name: v1alpha1 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + items: + properties: + clusterRoleName: + type: string + subjects: + description: kubebuilder:validation:Minimum=1 + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + required: + - clusterRoleName + - subjects + type: object + type: array + containerRegistries: properties: - clusterRoleName: - type: string - subjects: - description: kubebuilder:validation:Minimum=1 + allowed: items: - description: Subject contains a reference to the object or - user identities a role binding applies to. This can either - hold a direct API object reference, or a value for non-objects - such as user and group names. - properties: - apiGroup: - description: APIGroup holds the API group of the referenced - subject. Defaults to "" for ServiceAccount subjects. - Defaults to "rbac.authorization.k8s.io" for User and - Group subjects. - type: string - kind: - description: Kind of object being referenced. Values defined - by this API group are "User", "Group", and "ServiceAccount". - If the Authorizer does not recognized the kind value, - the Authorizer should report an error. - type: string - name: - description: Name of the object being referenced. - type: string - namespace: - description: Namespace of the referenced object. If the - object kind is non-namespace, such as "User" or "Group", - and this value is not empty the Authorizer should report - an error. - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic + type: string type: array - required: - - clusterRoleName - - subjects - type: object - type: array - containerRegistries: - properties: - allowed: - items: - type: string - type: array - allowedRegex: - type: string - type: object - externalServiceIPs: - properties: - allowed: - items: - pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + allowedRegex: type: string - type: array - required: - - allowed - type: object - ingressClasses: - properties: - allowed: - items: - type: string - type: array - allowedRegex: - type: string - type: object - ingressHostnames: - properties: - allowed: - items: - type: string - type: array - allowedRegex: - type: string - type: object - limitRanges: - items: - description: LimitRangeSpec defines a min/max usage limit for resources - that match on kind. + type: object + externalServiceIPs: properties: - limits: - description: Limits is the list of LimitRangeItem objects that - are enforced. + allowed: items: - description: LimitRangeItem defines a min/max usage limit - for any resource that matches on kind. - properties: - default: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Default resource requirement limit value - by resource name if resource limit is omitted. - type: object - defaultRequest: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement - request value by resource name if resource request is - omitted. - type: object - max: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource - name. - type: object - maxLimitRequestRatio: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named - resource must have a request and limit that are both - non-zero where limit divided by request is less than - or equal to the enumerated value; this represents the - max burst for the named resource. - type: object - min: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource - name. - type: object - type: - description: Type of resource that this limit applies - to. - type: string - required: - - type - type: object + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string type: array required: - - limits + - allowed type: object - type: array - namespaceQuota: - format: int32 - minimum: 1 - type: integer - namespacesMetadata: - properties: - additionalAnnotations: - additionalProperties: - type: string - type: object - additionalLabels: - additionalProperties: + ingressClasses: + properties: + allowed: + items: + type: string + type: array + allowedRegex: type: string - type: object - type: object - networkPolicies: - items: - description: NetworkPolicySpec provides the specification of a NetworkPolicy + type: object + ingressHostnames: properties: - egress: - description: List of egress rules to be applied to the selected - pods. Outgoing traffic is allowed if there are no NetworkPolicies - selecting the pod (and cluster policy otherwise allows the - traffic), OR if the traffic matches at least one egress rule - across all of the NetworkPolicy objects whose podSelector - matches the pod. If this field is empty then this NetworkPolicy - limits all outgoing traffic (and serves solely to ensure that - the pods it selects are isolated by default). This field is - beta-level in 1.8 + allowed: items: - description: NetworkPolicyEgressRule describes a particular - set of traffic that is allowed out of pods matched by a - NetworkPolicySpec's podSelector. The traffic must match - both ports and to. This type is beta-level in 1.8 - properties: - ports: - description: List of destination ports for outgoing traffic. - Each item in this list is combined using a logical OR. - If this field is empty or missing, this rule matches - all ports (traffic not restricted by port). If this - field is present and contains at least one item, then - this rule allows traffic only if the traffic matches - at least one port in the list. - items: - description: NetworkPolicyPort describes a port to allow - traffic on - properties: - endPort: - description: If set, indicates that the range of - ports from port to endPort, inclusive, should - be allowed by the policy. This field cannot be - defined if the port field is not defined or if - the port field is defined as a named (string) - port. The endPort must be equal or greater than - port. This feature is in Beta state and is enabled - by default. It can be disabled using the Feature - Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: + type: string + type: array + allowedRegex: + type: string + type: object + limitRanges: + items: + description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + properties: + limits: + description: Limits is the list of LimitRangeItem objects that are enforced. + items: + description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: - type: integer - type: string - description: The port on the given protocol. This - can either be a numerical or named port on a pod. - If this field is not provided, this matches all - port names and numbers. If present, only traffic - on the specified protocol AND port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) which - traffic must match. If not specified, this field - defaults to TCP. - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value by resource name if resource limit is omitted. type: object - type: array - to: - description: List of destinations for outgoing traffic - of pods selected for this rule. Items in this list are - combined using a logical OR operation. If this field - is empty or missing, this rule matches all destinations - (traffic not restricted by destination). If this field - is present and contains at least one item, this rule - allows traffic only if the traffic matches at least - one item in the to list. - items: - description: NetworkPolicyPeer describes a peer to allow - traffic to/from. Only certain combinations of fields - are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular - IPBlock. If this field is set then neither of - the other fields can be. - properties: - cidr: - description: CIDR is a string representing the - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" - type: string - except: - description: Except is a slice of CIDRs that - should not be included within an IP Block - Valid examples are "192.168.1.1/24" or "2001:db9::/64" - Except values will be rejected if they are - outside the CIDR range - items: + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by resource name. + type: object + type: + description: Type of resource that this limit applies to. + type: string + required: + - type + type: object + type: array + required: + - limits + type: object + type: array + namespaceQuota: + format: int32 + minimum: 1 + type: integer + namespacesMetadata: + properties: + additionalAnnotations: + additionalProperties: + type: string + type: object + additionalLabels: + additionalProperties: + type: string + type: object + type: object + networkPolicies: + items: + description: NetworkPolicySpec provides the specification of a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + properties: + ports: + description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + to: + description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label selector - semantics; if present but empty, it selects all - namespaces. \n If PodSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects all - Pods in the Namespaces selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which selects - Pods. This field follows standard label selector - semantics; if present but empty, it selects all - pods. \n If NamespaceSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects the - Pods matching PodSelector in the policy's own - Namespace." - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - type: object - type: array - ingress: - description: List of ingress rules to be applied to the selected - pods. Traffic is allowed to a pod if there are no NetworkPolicies - selecting the pod (and cluster policy otherwise allows the - traffic), OR if the traffic source is the pod's local node, - OR if the traffic matches at least one ingress rule across - all of the NetworkPolicy objects whose podSelector matches - the pod. If this field is empty then this NetworkPolicy does - not allow any traffic (and serves solely to ensure that the - pods it selects are isolated by default) - items: - description: NetworkPolicyIngressRule describes a particular - set of traffic that is allowed to the pods matched by a - NetworkPolicySpec's podSelector. The traffic must match - both ports and from. - properties: - from: - description: List of sources which should be able to access - the pods selected for this rule. Items in this list - are combined using a logical OR operation. If this field - is empty or missing, this rule matches all sources (traffic - not restricted by source). If this field is present - and contains at least one item, this rule allows traffic - only if the traffic matches at least one item in the - from list. - items: - description: NetworkPolicyPeer describes a peer to allow - traffic to/from. Only certain combinations of fields - are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular - IPBlock. If this field is set then neither of - the other fields can be. - properties: - cidr: - description: CIDR is a string representing the - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" - type: string - except: - description: Except is a slice of CIDRs that - should not be included within an IP Block - Valid examples are "192.168.1.1/24" or "2001:db9::/64" - Except values will be rejected if they are - outside the CIDR range - items: + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + properties: + from: + description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label selector - semantics; if present but empty, it selects all - namespaces. \n If PodSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects all - Pods in the Namespaces selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which selects - Pods. This field follows standard label selector - semantics; if present but empty, it selects all - pods. \n If NamespaceSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects the - Pods matching PodSelector in the policy's own - Namespace." - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - ports: - description: List of ports which should be made accessible - on the pods selected for this rule. Each item in this - list is combined using a logical OR. If this field is - empty or missing, this rule matches all ports (traffic - not restricted by port). If this field is present and - contains at least one item, then this rule allows traffic - only if the traffic matches at least one port in the - list. + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + type: object + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: - description: NetworkPolicyPort describes a port to allow - traffic on + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: - endPort: - description: If set, indicates that the range of - ports from port to endPort, inclusive, should - be allowed by the policy. This field cannot be - defined if the port field is not defined or if - the port field is defined as a named (string) - port. The endPort must be equal or greater than - port. This feature is in Beta state and is enabled - by default. It can be disabled using the Feature - Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - description: The port on the given protocol. This - can either be a numerical or named port on a pod. - If this field is not provided, this matches all - port names and numbers. If present, only traffic - on the specified protocol AND port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) which - traffic must match. If not specified, this field - defaults to TCP. + key: + description: key is the label key that the selector applies to. type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator type: object type: array - type: object - type: array - podSelector: - description: Selects the pods to which this NetworkPolicy object - applies. The array of ingress rules is applied to any pods - selected by this field. Multiple network policies can select - the same set of pods. In this case, the ingress rules for - each are combined additively. This field is NOT optional and - follows standard label selector semantics. An empty podSelector - matches all pods in this namespace. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, - Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists or - DoesNotExist, the values array must be empty. This - array is replaced during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - policyTypes: - description: List of rule types that the NetworkPolicy relates - to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", - "Egress"]. If this field is not specified, it will default - based on the existence of Ingress or Egress rules; policies - that contain an Egress section are assumed to affect Egress, - and all policies (whether or not they contain an Ingress section) - are assumed to affect Ingress. If you want to write an egress-only - policy, you must explicitly specify policyTypes [ "Egress" - ]. Likewise, if you want to write a policy that specifies - that no egress is allowed, you must specify a policyTypes - value that include "Egress" (since such a policy would not - include an Egress section and would otherwise default to just - [ "Ingress" ]). This field is beta-level in 1.8 - items: - description: PolicyType string describes the NetworkPolicy - type This type is beta-level in 1.8 - type: string - type: array - required: - - podSelector - type: object - type: array - nodeSelector: - additionalProperties: - type: string - type: object - owner: - description: OwnerSpec defines tenant owner name and kind. - properties: - kind: - enum: - - User - - Group - type: string - name: - type: string - required: - - kind - - name - type: object - resourceQuotas: - items: - description: ResourceQuotaSpec defines the desired hard limits to - enforce for Quota. - properties: - hard: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each - named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' - type: object - scopeSelector: - description: scopeSelector is also a collection of filters like - scopes that must match each object tracked by a quota but - expressed using ScopeSelectorOperator in combination with - possible values. For a resource to match, both scopes AND - scopeSelector (if specified in spec), must be matched. - properties: - matchExpressions: - description: A list of scope selector requirements by scope - of the resources. - items: - description: A scoped-resource selector requirement is - a selector that contains values, a scope name, and an - operator that relates the scope name and values. - properties: - operator: - description: Represents a scope's relationship to - a set of values. Valid operators are In, NotIn, - Exists, DoesNotExist. - type: string - scopeName: - description: The name of the scope that the selector - applies to. - type: string - values: - description: An array of string values. If the operator - is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - operator - - scopeName + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - type: object - x-kubernetes-map-type: atomic - scopes: - description: A collection of filters that must match each object - tracked by a quota. If not specified, the quota matches all - objects. - items: - description: A ResourceQuotaScope defines a filter that must - match each object tracked by a quota - type: string - type: array - type: object - type: array - servicesMetadata: - properties: - additionalAnnotations: - additionalProperties: - type: string - type: object - additionalLabels: - additionalProperties: - type: string + type: object + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector type: object - type: object - storageClasses: - properties: - allowed: - items: - type: string - type: array - allowedRegex: + type: array + nodeSelector: + additionalProperties: type: string - type: object - required: - - owner - type: object - status: - description: TenantStatus defines the observed state of Tenant. - properties: - namespaces: - items: - type: string - type: array - size: - type: integer - required: - - size - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: The actual state of the Tenant - jsonPath: .status.state - name: State - type: string - - description: The max amount of Namespaces can be created - jsonPath: .spec.namespaceOptions.quota - name: Namespace quota - type: integer - - description: The total amount of Namespaces in use - jsonPath: .status.size - name: Namespace count - type: integer - - description: Node Selector applied to Pods - jsonPath: .spec.nodeSelector - name: Node selector - type: string - - description: Age - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Tenant is the Schema for the tenants API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TenantSpec defines the desired state of Tenant. - properties: - additionalRoleBindings: - description: Specifies additional RoleBindings assigned to the Tenant. - Capsule will ensure that all namespaces in the Tenant always contain - the RoleBinding for the given ClusterRole. Optional. - items: + type: object + owner: + description: OwnerSpec defines tenant owner name and kind. properties: - clusterRoleName: + kind: + enum: + - User + - Group + type: string + name: type: string - subjects: - description: kubebuilder:validation:Minimum=1 - items: - description: Subject contains a reference to the object or - user identities a role binding applies to. This can either - hold a direct API object reference, or a value for non-objects - such as user and group names. - properties: - apiGroup: - description: APIGroup holds the API group of the referenced - subject. Defaults to "" for ServiceAccount subjects. - Defaults to "rbac.authorization.k8s.io" for User and - Group subjects. - type: string - kind: - description: Kind of object being referenced. Values defined - by this API group are "User", "Group", and "ServiceAccount". - If the Authorizer does not recognized the kind value, - the Authorizer should report an error. - type: string - name: - description: Name of the object being referenced. - type: string - namespace: - description: Namespace of the referenced object. If the - object kind is non-namespace, such as "User" or "Group", - and this value is not empty the Authorizer should report - an error. - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - type: array required: - - clusterRoleName - - subjects + - kind + - name type: object - type: array - containerRegistries: - description: Specifies the trusted Image Registries assigned to the - Tenant. Capsule assures that all Pods resources created in the Tenant - can use only one of the allowed trusted registries. Optional. - properties: - allowed: - items: - type: string - type: array - allowedRegex: - type: string - type: object - imagePullPolicies: - description: Specify the allowed values for the imagePullPolicies - option in Pod resources. Capsule assures that all Pod resources - created in the Tenant can use only one of the allowed policy. Optional. - items: - enum: - - Always - - Never - - IfNotPresent - type: string - type: array - ingressOptions: - description: Specifies options for the Ingress resources, such as - allowed hostnames and IngressClass. Optional. - properties: - allowedClasses: - description: Specifies the allowed IngressClasses assigned to - the Tenant. Capsule assures that all Ingress resources created - in the Tenant can use only one of the allowed IngressClasses. - Optional. + resourceQuotas: + items: + description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. properties: - allowed: + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by scope of the resources. + items: + description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + properties: + operator: + description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector applies to. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. items: + description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota type: string type: array - allowedRegex: - type: string type: object - allowedHostnames: - description: Specifies the allowed hostnames in Ingresses for - the given Tenant. Capsule assures that all Ingress resources - created in the Tenant can use only one of the allowed hostnames. - Optional. - properties: - allowed: - items: - type: string - type: array - allowedRegex: + type: array + servicesMetadata: + properties: + additionalAnnotations: + additionalProperties: type: string - type: object - hostnameCollisionScope: - default: Disabled - description: "Defines the scope of hostname collision check performed - when Tenant Owners create Ingress with allowed hostnames. \n - - Cluster: disallow the creation of an Ingress if the pair hostname - and path is already used across the Namespaces managed by Capsule. - \n - Tenant: disallow the creation of an Ingress if the pair - hostname and path is already used across the Namespaces of the - Tenant. \n - Namespace: disallow the creation of an Ingress - if the pair hostname and path is already used in the Ingress - Namespace. \n Optional." - enum: - - Cluster - - Tenant - - Namespace - - Disabled + type: object + additionalLabels: + additionalProperties: + type: string + type: object + type: object + storageClasses: + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + required: + - owner + type: object + status: + description: TenantStatus defines the observed state of Tenant. + properties: + namespaces: + items: type: string - type: object - limitRanges: - description: Specifies the resource min/max usage restrictions to - the Tenant. The assigned values are inherited by any namespace created - in the Tenant. Optional. - properties: + type: array + size: + type: integer + required: + - size + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The actual state of the Tenant + jsonPath: .status.state + name: State + type: string + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceOptions.quota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. items: - items: - description: LimitRangeSpec defines a min/max usage limit for - resources that match on kind. - properties: - limits: - description: Limits is the list of LimitRangeItem objects - that are enforced. - items: - description: LimitRangeItem defines a min/max usage limit - for any resource that matches on kind. - properties: - default: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Default resource requirement limit value - by resource name if resource limit is omitted. - type: object - defaultRequest: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource - requirement request value by resource name if resource - request is omitted. - type: object - max: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by - resource name. - type: object - maxLimitRequestRatio: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the - named resource must have a request and limit that - are both non-zero where limit divided by request - is less than or equal to the enumerated value; this - represents the max burst for the named resource. - type: object - min: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by - resource name. - type: object - type: - description: Type of resource that this limit applies - to. - type: string - required: - - type - type: object - type: array - required: - - limits - type: object - type: array - type: object - namespaceOptions: - description: Specifies options for the Namespaces, such as additional - metadata or maximum number of namespaces allowed for that Tenant. - Once the namespace quota assigned to the Tenant has been reached, - the Tenant owner cannot create further namespaces. Optional. - properties: - additionalMetadata: - description: Specifies additional labels and annotations the Capsule - operator places on any Namespace resource in the Tenant. Optional. - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object + properties: + clusterRoleName: + type: string + subjects: + description: kubebuilder:validation:Minimum=1 + items: + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + properties: + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + type: string + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + type: array + required: + - clusterRoleName + - subjects type: object - quota: - description: Specifies the maximum number of namespaces allowed - for that Tenant. Once the namespace quota assigned to the Tenant - has been reached, the Tenant owner cannot create further namespaces. - Optional. - format: int32 - minimum: 1 - type: integer - type: object - networkPolicies: - description: Specifies the NetworkPolicies assigned to the Tenant. - The assigned NetworkPolicies are inherited by any namespace created - in the Tenant. Optional. - properties: + type: array + containerRegistries: + description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. items: - items: - description: NetworkPolicySpec provides the specification of - a NetworkPolicy + enum: + - Always + - Never + - IfNotPresent + type: string + type: array + ingressOptions: + description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + properties: + allowedClasses: + description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. properties: - egress: - description: List of egress rules to be applied to the selected - pods. Outgoing traffic is allowed if there are no NetworkPolicies - selecting the pod (and cluster policy otherwise allows - the traffic), OR if the traffic matches at least one egress - rule across all of the NetworkPolicy objects whose podSelector - matches the pod. If this field is empty then this NetworkPolicy - limits all outgoing traffic (and serves solely to ensure - that the pods it selects are isolated by default). This - field is beta-level in 1.8 + allowed: items: - description: NetworkPolicyEgressRule describes a particular - set of traffic that is allowed out of pods matched by - a NetworkPolicySpec's podSelector. The traffic must - match both ports and to. This type is beta-level in - 1.8 - properties: - ports: - description: List of destination ports for outgoing - traffic. Each item in this list is combined using - a logical OR. If this field is empty or missing, - this rule matches all ports (traffic not restricted - by port). If this field is present and contains - at least one item, then this rule allows traffic - only if the traffic matches at least one port in - the list. - items: - description: NetworkPolicyPort describes a port - to allow traffic on - properties: - endPort: - description: If set, indicates that the range - of ports from port to endPort, inclusive, - should be allowed by the policy. This field - cannot be defined if the port field is not - defined or if the port field is defined as - a named (string) port. The endPort must be - equal or greater than port. This feature is - in Beta state and is enabled by default. It - can be disabled using the Feature Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: + type: string + type: array + allowedRegex: + type: string + type: object + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + enum: + - Cluster + - Tenant + - Namespace + - Disabled + type: string + type: object + limitRanges: + description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + properties: + limits: + description: Limits is the list of LimitRangeItem objects that are enforced. + items: + description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: - type: integer - type: string - description: The port on the given protocol. - This can either be a numerical or named port - on a pod. If this field is not provided, this - matches all port names and numbers. If present, - only traffic on the specified protocol AND - port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) - which traffic must match. If not specified, - this field defaults to TCP. - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value by resource name if resource limit is omitted. type: object - type: array - to: - description: List of destinations for outgoing traffic - of pods selected for this rule. Items in this list - are combined using a logical OR operation. If this - field is empty or missing, this rule matches all - destinations (traffic not restricted by destination). - If this field is present and contains at least one - item, this rule allows traffic only if the traffic - matches at least one item in the to list. - items: - description: NetworkPolicyPeer describes a peer - to allow traffic to/from. Only certain combinations - of fields are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular - IPBlock. If this field is set then neither - of the other fields can be. - properties: - cidr: - description: CIDR is a string representing - the IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" - type: string - except: - description: Except is a slice of CIDRs - that should not be included within an - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" Except values will - be rejected if they are outside the CIDR - range - items: + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by resource name. + type: object + type: + description: Type of resource that this limit applies to. + type: string + required: + - type + type: object + type: array + required: + - limits + type: object + type: array + type: object + namespaceOptions: + description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + quota: + description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + format: int32 + minimum: 1 + type: integer + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: NetworkPolicySpec provides the specification of a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + properties: + ports: + description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + to: + description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label - selector semantics; if present but empty, - it selects all namespaces. \n If PodSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects all Pods in the Namespaces - selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which - selects Pods. This field follows standard - label selector semantics; if present but empty, - it selects all pods. \n If NamespaceSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects the Pods matching PodSelector - in the policy's own Namespace." - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - type: object - type: array - ingress: - description: List of ingress rules to be applied to the - selected pods. Traffic is allowed to a pod if there are - no NetworkPolicies selecting the pod (and cluster policy - otherwise allows the traffic), OR if the traffic source - is the pod's local node, OR if the traffic matches at - least one ingress rule across all of the NetworkPolicy - objects whose podSelector matches the pod. If this field - is empty then this NetworkPolicy does not allow any traffic - (and serves solely to ensure that the pods it selects - are isolated by default) - items: - description: NetworkPolicyIngressRule describes a particular - set of traffic that is allowed to the pods matched by - a NetworkPolicySpec's podSelector. The traffic must - match both ports and from. - properties: - from: - description: List of sources which should be able - to access the pods selected for this rule. Items - in this list are combined using a logical OR operation. - If this field is empty or missing, this rule matches - all sources (traffic not restricted by source). - If this field is present and contains at least one - item, this rule allows traffic only if the traffic - matches at least one item in the from list. - items: - description: NetworkPolicyPeer describes a peer - to allow traffic to/from. Only certain combinations - of fields are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular - IPBlock. If this field is set then neither - of the other fields can be. - properties: - cidr: - description: CIDR is a string representing - the IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" - type: string - except: - description: Except is a slice of CIDRs - that should not be included within an - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" Except values will - be rejected if they are outside the CIDR - range - items: + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + properties: + from: + description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label - selector semantics; if present but empty, - it selects all namespaces. \n If PodSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects all Pods in the Namespaces - selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which - selects Pods. This field follows standard - label selector semantics; if present but empty, - it selects all pods. \n If NamespaceSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects the Pods matching PodSelector - in the policy's own Namespace." - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - ports: - description: List of ports which should be made accessible - on the pods selected for this rule. Each item in - this list is combined using a logical OR. If this - field is empty or missing, this rule matches all - ports (traffic not restricted by port). If this - field is present and contains at least one item, - then this rule allows traffic only if the traffic - matches at least one port in the list. + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + type: object + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: - description: NetworkPolicyPort describes a port - to allow traffic on + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: - endPort: - description: If set, indicates that the range - of ports from port to endPort, inclusive, - should be allowed by the policy. This field - cannot be defined if the port field is not - defined or if the port field is defined as - a named (string) port. The endPort must be - equal or greater than port. This feature is - in Beta state and is enabled by default. It - can be disabled using the Feature Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - description: The port on the given protocol. - This can either be a numerical or named port - on a pod. If this field is not provided, this - matches all port names and numbers. If present, - only traffic on the specified protocol AND - port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) - which traffic must match. If not specified, - this field defaults to TCP. + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator type: object type: array - type: object - type: array - podSelector: - description: Selects the pods to which this NetworkPolicy - object applies. The array of ingress rules is applied - to any pods selected by this field. Multiple network policies - can select the same set of pods. In this case, the ingress - rules for each are combined additively. This field is - NOT optional and follows standard label selector semantics. - An empty podSelector matches all pods in this namespace. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - policyTypes: - description: List of rule types that the NetworkPolicy relates - to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", - "Egress"]. If this field is not specified, it will default - based on the existence of Ingress or Egress rules; policies - that contain an Egress section are assumed to affect Egress, - and all policies (whether or not they contain an Ingress - section) are assumed to affect Ingress. If you want to - write an egress-only policy, you must explicitly specify - policyTypes [ "Egress" ]. Likewise, if you want to write - a policy that specifies that no egress is allowed, you - must specify a policyTypes value that include "Egress" - (since such a policy would not include an Egress section - and would otherwise default to just [ "Ingress" ]). This - field is beta-level in 1.8 - items: - description: PolicyType string describes the NetworkPolicy - type This type is beta-level in 1.8 - type: string - type: array - required: - - podSelector - type: object - type: array - type: object - nodeSelector: - additionalProperties: - type: string - description: Specifies the label to control the placement of pods - on a given pool of worker nodes. All namespaces created within the - Tenant will have the node selector annotation. This annotation tells - the Kubernetes scheduler to place pods on the nodes having the selector - label. Optional. - type: object - owners: - description: Specifies the owners of the Tenant. Mandatory. - items: - properties: - kind: - description: Kind of tenant owner. Possible values are "User", - "Group", and "ServiceAccount" - enum: - - User - - Group - - ServiceAccount - type: string - name: - description: Name of tenant owner. - type: string - proxySettings: - description: Proxy settings for tenant owner. - items: - properties: - kind: - enum: - - Nodes - - StorageClasses - - IngressClasses - - PriorityClasses - type: string - operations: + type: object + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: - enum: - - List - - Update - - Delete + description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 type: string type: array required: - - kind - - operations + - podSelector type: object type: array - required: - - kind - - name type: object - type: array - priorityClasses: - description: Specifies the allowed priorityClasses assigned to the - Tenant. Capsule assures that all Pods resources created in the Tenant - can use only one of the allowed PriorityClasses. Optional. - properties: - allowed: - items: - type: string - type: array - allowedRegex: + nodeSelector: + additionalProperties: type: string - type: object - resourceQuotas: - description: Specifies a list of ResourceQuota resources assigned - to the Tenant. The assigned values are inherited by any namespace - created in the Tenant. The Capsule operator aggregates ResourceQuota - at Tenant level, so that the hard quota is never crossed for the - given Tenant. This permits the Tenant owner to consume resources - in the Tenant regardless of the namespace. Optional. - properties: + description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. items: - items: - description: ResourceQuotaSpec defines the desired hard limits - to enforce for Quota. - properties: - hard: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for - each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' - type: object - scopeSelector: - description: scopeSelector is also a collection of filters - like scopes that must match each object tracked by a quota - but expressed using ScopeSelectorOperator in combination - with possible values. For a resource to match, both scopes - AND scopeSelector (if specified in spec), must be matched. + properties: + kind: + description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of tenant owner. + type: string + proxySettings: + description: Proxy settings for tenant owner. + items: properties: - matchExpressions: - description: A list of scope selector requirements by - scope of the resources. + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses + type: string + operations: items: - description: A scoped-resource selector requirement - is a selector that contains values, a scope name, - and an operator that relates the scope name and - values. - properties: - operator: - description: Represents a scope's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. - type: string - scopeName: - description: The name of the scope that the selector - applies to. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array must - be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - operator - - scopeName - type: object + enum: + - List + - Update + - Delete + type: string type: array - type: object - x-kubernetes-map-type: atomic - scopes: - description: A collection of filters that must match each - object tracked by a quota. If not specified, the quota - matches all objects. - items: - description: A ResourceQuotaScope defines a filter that - must match each object tracked by a quota - type: string - type: array - type: object - type: array - scope: - default: Tenant - description: Define if the Resource Budget should compute resource - across all Namespaces in the Tenant or individually per cluster. - Default is Tenant - enum: - - Tenant - - Namespace - type: string - type: object - serviceOptions: - description: Specifies options for the Service, such as additional - metadata or block of certain type of Services. Optional. - properties: - additionalMetadata: - description: Specifies additional labels and annotations the Capsule - operator places on any Service resource in the Tenant. Optional. - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - allowedServices: - description: Block or deny certain type of Services. Optional. - properties: - externalName: - default: true - description: Specifies if ExternalName service type resources - are allowed for the Tenant. Default is true. Optional. - type: boolean - loadBalancer: - default: true - description: Specifies if LoadBalancer service type resources - are allowed for the Tenant. Default is true. Optional. - type: boolean - nodePort: - default: true - description: Specifies if NodePort service type resources - are allowed for the Tenant. Default is true. Optional. - type: boolean - type: object - externalIPs: - description: Specifies the external IPs that can be used in Services - with type ClusterIP. An empty list means no IPs are allowed. - Optional. - properties: - allowed: - items: - pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ - type: string + required: + - kind + - operations + type: object type: array required: - - allowed + - kind + - name type: object - type: object - storageClasses: - description: Specifies the allowed StorageClasses assigned to the - Tenant. Capsule assures that all PersistentVolumeClaim resources - created in the Tenant can use only one of the allowed StorageClasses. - Optional. - properties: - allowed: - items: - type: string - type: array - allowedRegex: - type: string - type: object - required: - - owners - type: object - status: - description: Returns the observed state of the Tenant. - properties: - namespaces: - description: List of namespaces assigned to the Tenant. - items: - type: string - type: array - size: - description: How many namespaces are assigned to the Tenant. - type: integer - state: - default: Active - description: The operational state of the Tenant. Possible values - are "Active", "Cordoned". - enum: - - Cordoned - - Active - type: string - required: - - size - - state - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: The actual state of the Tenant - jsonPath: .status.state - name: State - type: string - - description: The max amount of Namespaces can be created - jsonPath: .spec.namespaceOptions.quota - name: Namespace quota - type: integer - - description: The total amount of Namespaces in use - jsonPath: .status.size - name: Namespace count - type: integer - - description: Node Selector applied to Pods - jsonPath: .spec.nodeSelector - name: Node selector - type: string - - description: Age - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta2 - schema: - openAPIV3Schema: - description: Tenant is the Schema for the tenants API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TenantSpec defines the desired state of Tenant. - properties: - additionalRoleBindings: - description: Specifies additional RoleBindings assigned to the Tenant. - Capsule will ensure that all namespaces in the Tenant always contain - the RoleBinding for the given ClusterRole. Optional. - items: + type: array + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. properties: - clusterRoleName: + allowed: + items: + type: string + type: array + allowedRegex: type: string - subjects: - description: kubebuilder:validation:Minimum=1 + type: object + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + properties: + items: items: - description: Subject contains a reference to the object or - user identities a role binding applies to. This can either - hold a direct API object reference, or a value for non-objects - such as user and group names. + description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. properties: - apiGroup: - description: APIGroup holds the API group of the referenced - subject. Defaults to "" for ServiceAccount subjects. - Defaults to "rbac.authorization.k8s.io" for User and - Group subjects. - type: string - kind: - description: Kind of object being referenced. Values defined - by this API group are "User", "Group", and "ServiceAccount". - If the Authorizer does not recognized the kind value, - the Authorizer should report an error. + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by scope of the resources. + items: + description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + properties: + operator: + description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector applies to. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + items: + description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + type: string + type: array + type: object + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + enum: + - Tenant + - Namespace + type: string + type: object + serviceOptions: + description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: type: string - name: - description: Name of the object being referenced. + type: object + labels: + additionalProperties: type: string - namespace: - description: Namespace of the referenced object. If the - object kind is non-namespace, such as "User" or "Group", - and this value is not empty the Authorizer should report - an error. + type: object + type: object + allowedServices: + description: Block or deny certain type of Services. Optional. + properties: + externalName: + default: true + description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - type: array - required: - - clusterRoleName - - subjects + type: array + required: + - allowed + type: object type: object - type: array - containerRegistries: - description: Specifies the trusted Image Registries assigned to the - Tenant. Capsule assures that all Pods resources created in the Tenant - can use only one of the allowed trusted registries. Optional. - properties: - allowed: - items: + storageClasses: + description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: type: string - type: array - allowedRegex: + type: object + required: + - owners + type: object + status: + description: Returns the observed state of the Tenant. + properties: + namespaces: + description: List of namespaces assigned to the Tenant. + items: type: string - type: object - cordoned: - description: Toggling the Tenant resources cordoning, when enable - resources cannot be deleted. - type: boolean - imagePullPolicies: - description: Specify the allowed values for the imagePullPolicies - option in Pod resources. Capsule assures that all Pod resources - created in the Tenant can use only one of the allowed policy. Optional. - items: + type: array + size: + description: How many namespaces are assigned to the Tenant. + type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values are "Active", "Cordoned". enum: - - Always - - Never - - IfNotPresent + - Cordoned + - Active type: string - type: array - ingressOptions: - description: Specifies options for the Ingress resources, such as - allowed hostnames and IngressClass. Optional. - properties: - allowWildcardHostnames: - description: Toggles the ability for Ingress resources created - in a Tenant to have a hostname wildcard. - type: boolean - allowedClasses: - description: Specifies the allowed IngressClasses assigned to - the Tenant. Capsule assures that all Ingress resources created - in the Tenant can use only one of the allowed IngressClasses. - Optional. + required: + - size + - state + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The actual state of the Tenant + jsonPath: .status.state + name: State + type: string + - description: The max amount of Namespaces can be created + jsonPath: .spec.namespaceOptions.quota + name: Namespace quota + type: integer + - description: The total amount of Namespaces in use + jsonPath: .status.size + name: Namespace count + type: integer + - description: Node Selector applied to Pods + jsonPath: .spec.nodeSelector + name: Node selector + type: string + - description: Age + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant. + properties: + additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. + items: properties: - allowed: - items: - type: string - type: array - allowedRegex: + clusterRoleName: type: string - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. + subjects: + description: kubebuilder:validation:Minimum=1 items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that relates - the key and values. + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. properties: - key: - description: key is the label key that the selector - applies to. + apiGroup: + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, - Exists and DoesNotExist. + kind: + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + type: string + name: + description: Name of the object being referenced. + type: string + namespace: + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. type: string - values: - description: values is an array of string values. If - the operator is In or NotIn, the values array must - be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced - during a strategic merge patch. - items: - type: string - type: array required: - - key - - operator + - kind + - name type: object + x-kubernetes-map-type: atomic type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A - single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is "key", - the operator is "In", and the values array contains only - "value". The requirements are ANDed. - type: object + required: + - clusterRoleName + - subjects type: object - x-kubernetes-map-type: atomic - allowedHostnames: - description: Specifies the allowed hostnames in Ingresses for - the given Tenant. Capsule assures that all Ingress resources - created in the Tenant can use only one of the allowed hostnames. - Optional. - properties: - allowed: - items: - type: string - type: array - allowedRegex: + type: array + containerRegistries: + description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + properties: + allowed: + items: type: string - type: object - hostnameCollisionScope: - default: Disabled - description: "Defines the scope of hostname collision check performed - when Tenant Owners create Ingress with allowed hostnames. \n - - Cluster: disallow the creation of an Ingress if the pair hostname - and path is already used across the Namespaces managed by Capsule. - \n - Tenant: disallow the creation of an Ingress if the pair - hostname and path is already used across the Namespaces of the - Tenant. \n - Namespace: disallow the creation of an Ingress - if the pair hostname and path is already used in the Ingress - Namespace. \n Optional." + type: array + allowedRegex: + type: string + type: object + cordoned: + description: Toggling the Tenant resources cordoning, when enable resources cannot be deleted. + type: boolean + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. + items: enum: - - Cluster - - Tenant - - Namespace - - Disabled + - Always + - Never + - IfNotPresent type: string - required: - - allowWildcardHostnames - type: object - limitRanges: - description: Specifies the resource min/max usage restrictions to - the Tenant. The assigned values are inherited by any namespace created - in the Tenant. Optional. - properties: - items: - items: - description: LimitRangeSpec defines a min/max usage limit for - resources that match on kind. + type: array + ingressOptions: + description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + properties: + allowWildcardHostnames: + description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. + type: boolean + allowedClasses: + description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. A default value can be specified, and all the Ingress resources created will inherit the declared class. Optional. properties: - limits: - description: Limits is the list of LimitRangeItem objects - that are enforced. + allowed: + items: + type: string + type: array + allowedRegex: + type: string + default: + type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: - description: LimitRangeItem defines a min/max usage limit - for any resource that matches on kind. + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: - default: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Default resource requirement limit value - by resource name if resource limit is omitted. - type: object - defaultRequest: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource - requirement request value by resource name if resource - request is omitted. - type: object - max: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by - resource name. - type: object - maxLimitRequestRatio: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the - named resource must have a request and limit that - are both non-zero where limit divided by request - is less than or equal to the enumerated value; this - represents the max burst for the named resource. - type: object - min: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by - resource name. - type: object - type: - description: Type of resource that this limit applies - to. + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array required: - - type + - key + - operator type: object type: array - required: - - limits + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - type: object - namespaceOptions: - description: Specifies options for the Namespaces, such as additional - metadata or maximum number of namespaces allowed for that Tenant. - Once the namespace quota assigned to the Tenant has been reached, - the Tenant owner cannot create further namespaces. Optional. - properties: - additionalMetadata: - description: Specifies additional labels and annotations the Capsule - operator places on any Namespace resource in the Tenant. Optional. - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - forbiddenAnnotations: - description: Define the annotations that a Tenant Owner cannot - set for their Namespace resources. - properties: - denied: - items: - type: string - type: array - deniedRegex: - type: string - type: object - forbiddenLabels: - description: Define the labels that a Tenant Owner cannot set - for their Namespace resources. - properties: - denied: - items: - type: string - type: array - deniedRegex: - type: string - type: object - quota: - description: Specifies the maximum number of namespaces allowed - for that Tenant. Once the namespace quota assigned to the Tenant - has been reached, the Tenant owner cannot create further namespaces. - Optional. - format: int32 - minimum: 1 - type: integer - required: - - forbiddenAnnotations - - forbiddenLabels - type: object - networkPolicies: - description: Specifies the NetworkPolicies assigned to the Tenant. - The assigned NetworkPolicies are inherited by any namespace created - in the Tenant. Optional. - properties: - items: - items: - description: NetworkPolicySpec provides the specification of - a NetworkPolicy + x-kubernetes-map-type: atomic + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. properties: - egress: - description: List of egress rules to be applied to the selected - pods. Outgoing traffic is allowed if there are no NetworkPolicies - selecting the pod (and cluster policy otherwise allows - the traffic), OR if the traffic matches at least one egress - rule across all of the NetworkPolicy objects whose podSelector - matches the pod. If this field is empty then this NetworkPolicy - limits all outgoing traffic (and serves solely to ensure - that the pods it selects are isolated by default). This - field is beta-level in 1.8 + allowed: items: - description: NetworkPolicyEgressRule describes a particular - set of traffic that is allowed out of pods matched by - a NetworkPolicySpec's podSelector. The traffic must - match both ports and to. This type is beta-level in - 1.8 - properties: - ports: - description: List of destination ports for outgoing - traffic. Each item in this list is combined using - a logical OR. If this field is empty or missing, - this rule matches all ports (traffic not restricted - by port). If this field is present and contains - at least one item, then this rule allows traffic - only if the traffic matches at least one port in - the list. - items: - description: NetworkPolicyPort describes a port - to allow traffic on - properties: - endPort: - description: If set, indicates that the range - of ports from port to endPort, inclusive, - should be allowed by the policy. This field - cannot be defined if the port field is not - defined or if the port field is defined as - a named (string) port. The endPort must be - equal or greater than port. This feature is - in Beta state and is enabled by default. It - can be disabled using the Feature Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: + type: string + type: array + allowedRegex: + type: string + type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + enum: + - Cluster + - Tenant + - Namespace + - Disabled + type: string + required: + - allowWildcardHostnames + type: object + limitRanges: + description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + properties: + limits: + description: Limits is the list of LimitRangeItem objects that are enforced. + items: + description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + properties: + default: + additionalProperties: + anyOf: - type: integer - type: string - description: The port on the given protocol. - This can either be a numerical or named port - on a pod. If this field is not provided, this - matches all port names and numbers. If present, - only traffic on the specified protocol AND - port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) - which traffic must match. If not specified, - this field defaults to TCP. - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Default resource requirement limit value by resource name if resource limit is omitted. type: object - type: array - to: - description: List of destinations for outgoing traffic - of pods selected for this rule. Items in this list - are combined using a logical OR operation. If this - field is empty or missing, this rule matches all - destinations (traffic not restricted by destination). - If this field is present and contains at least one - item, this rule allows traffic only if the traffic - matches at least one item in the to list. - items: - description: NetworkPolicyPeer describes a peer - to allow traffic to/from. Only certain combinations - of fields are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular - IPBlock. If this field is set then neither - of the other fields can be. - properties: - cidr: - description: CIDR is a string representing - the IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" - type: string - except: - description: Except is a slice of CIDRs - that should not be included within an - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" Except values will - be rejected if they are outside the CIDR - range - items: + defaultRequest: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by resource name. + type: object + type: + description: Type of resource that this limit applies to. + type: string + required: + - type + type: object + type: array + required: + - limits + type: object + type: array + type: object + namespaceOptions: + description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + quota: + description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + format: int32 + minimum: 1 + type: integer + required: + - forbiddenAnnotations + - forbiddenLabels + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: NetworkPolicySpec provides the specification of a NetworkPolicy + properties: + egress: + description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + properties: + ports: + description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + to: + description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label - selector semantics; if present but empty, - it selects all namespaces. \n If PodSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects all Pods in the Namespaces - selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which - selects Pods. This field follows standard - label selector semantics; if present but empty, - it selects all pods. \n If NamespaceSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects the Pods matching PodSelector - in the policy's own Namespace." - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - type: object - type: array - ingress: - description: List of ingress rules to be applied to the - selected pods. Traffic is allowed to a pod if there are - no NetworkPolicies selecting the pod (and cluster policy - otherwise allows the traffic), OR if the traffic source - is the pod's local node, OR if the traffic matches at - least one ingress rule across all of the NetworkPolicy - objects whose podSelector matches the pod. If this field - is empty then this NetworkPolicy does not allow any traffic - (and serves solely to ensure that the pods it selects - are isolated by default) - items: - description: NetworkPolicyIngressRule describes a particular - set of traffic that is allowed to the pods matched by - a NetworkPolicySpec's podSelector. The traffic must - match both ports and from. - properties: - from: - description: List of sources which should be able - to access the pods selected for this rule. Items - in this list are combined using a logical OR operation. - If this field is empty or missing, this rule matches - all sources (traffic not restricted by source). - If this field is present and contains at least one - item, this rule allows traffic only if the traffic - matches at least one item in the from list. - items: - description: NetworkPolicyPeer describes a peer - to allow traffic to/from. Only certain combinations - of fields are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular - IPBlock. If this field is set then neither - of the other fields can be. - properties: - cidr: - description: CIDR is a string representing - the IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" - type: string - except: - description: Except is a slice of CIDRs - that should not be included within an - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" Except values will - be rejected if they are outside the CIDR - range - items: + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + properties: + from: + description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label - selector semantics; if present but empty, - it selects all namespaces. \n If PodSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects all Pods in the Namespaces - selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: + except: + description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which - selects Pods. This field follows standard - label selector semantics; if present but empty, - it selects all pods. \n If NamespaceSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects the Pods matching PodSelector - in the policy's own Namespace." - properties: - matchExpressions: - description: matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: A label selector requirement - is a selector that contains values, - a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key - that the selector applies to. - type: string - operator: - description: operator represents a - key's relationship to a set of values. - Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of - string values. If the operator is - In or NotIn, the values array must - be non-empty. If the operator is - Exists or DoesNotExist, the values - array must be empty. This array - is replaced during a strategic merge - patch. - items: + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator - is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - ports: - description: List of ports which should be made accessible - on the pods selected for this rule. Each item in - this list is combined using a logical OR. If this - field is empty or missing, this rule matches all - ports (traffic not restricted by port). If this - field is present and contains at least one item, - then this rule allows traffic only if the traffic - matches at least one port in the list. + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + type: object + type: array + podSelector: + description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: - description: NetworkPolicyPort describes a port - to allow traffic on + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: - endPort: - description: If set, indicates that the range - of ports from port to endPort, inclusive, - should be allowed by the policy. This field - cannot be defined if the port field is not - defined or if the port field is defined as - a named (string) port. The endPort must be - equal or greater than port. This feature is - in Beta state and is enabled by default. It - can be disabled using the Feature Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - description: The port on the given protocol. - This can either be a numerical or named port - on a pod. If this field is not provided, this - matches all port names and numbers. If present, - only traffic on the specified protocol AND - port will be matched. - x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) - which traffic must match. If not specified, - this field defaults to TCP. + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator type: object type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - podSelector: - description: Selects the pods to which this NetworkPolicy - object applies. The array of ingress rules is applied - to any pods selected by this field. Multiple network policies - can select the same set of pods. In this case, the ingress - rules for each are combined additively. This field is - NOT optional and follows standard label selector semantics. - An empty podSelector matches all pods in this namespace. + x-kubernetes-map-type: atomic + policyTypes: + description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector + type: object + type: array + type: object + nodeSelector: + additionalProperties: + type: string + description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. + items: + properties: + clusterRoles: + default: + - admin + - capsule-namespace-deleter + description: Defines additional cluster-roles for the specific Owner. + items: + type: string + type: array + kind: + description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of tenant owner. + type: string + proxySettings: + description: Proxy settings for tenant owner. + items: properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses + type: string + operations: items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, - NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists - or DoesNotExist, the values array must be empty. - This array is replaced during a strategic merge - patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: + enum: + - List + - Update + - Delete type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field - is "key", the operator is "In", and the values array - contains only "value". The requirements are ANDed. - type: object + type: array + required: + - kind + - operations type: object - x-kubernetes-map-type: atomic - policyTypes: - description: List of rule types that the NetworkPolicy relates - to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", - "Egress"]. If this field is not specified, it will default - based on the existence of Ingress or Egress rules; policies - that contain an Egress section are assumed to affect Egress, - and all policies (whether or not they contain an Ingress - section) are assumed to affect Ingress. If you want to - write an egress-only policy, you must explicitly specify - policyTypes [ "Egress" ]. Likewise, if you want to write - a policy that specifies that no egress is allowed, you - must specify a policyTypes value that include "Egress" - (since such a policy would not include an Egress section - and would otherwise default to just [ "Ingress" ]). This - field is beta-level in 1.8 - items: - description: PolicyType string describes the NetworkPolicy - type This type is beta-level in 1.8 - type: string - type: array - required: - - podSelector - type: object - type: array - type: object - nodeSelector: - additionalProperties: - type: string - description: Specifies the label to control the placement of pods - on a given pool of worker nodes. All namespaces created within the - Tenant will have the node selector annotation. This annotation tells - the Kubernetes scheduler to place pods on the nodes having the selector - label. Optional. - type: object - owners: - description: Specifies the owners of the Tenant. Mandatory. - items: + type: array + required: + - kind + - name + type: object + type: array + preventDeletion: + description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined. + type: boolean + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. A default value can be specified, and all the Pod resources created will inherit the declared class. Optional. properties: - clusterRoles: - default: - - admin - - capsule-namespace-deleter - description: Defines additional cluster-roles for the specific - Owner. + allowed: items: type: string type: array - kind: - description: Kind of tenant owner. Possible values are "User", - "Group", and "ServiceAccount" - enum: - - User - - Group - - ServiceAccount + allowedRegex: type: string - name: - description: Name of tenant owner. + default: type: string - proxySettings: - description: Proxy settings for tenant owner. + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: - kind: - enum: - - Nodes - - StorageClasses - - IngressClasses - - PriorityClasses + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string - operations: + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. items: - enum: - - List - - Update - - Delete type: string type: array required: - - kind - - operations + - key + - operator type: object type: array - required: - - kind - - name + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - preventDeletion: - description: Prevent accidental deletion of the Tenant. When enabled, - the deletion request will be declined. - type: boolean - priorityClasses: - description: Specifies the allowed priorityClasses assigned to the - Tenant. Capsule assures that all Pods resources created in the Tenant - can use only one of the allowed PriorityClasses. Optional. - properties: - allowed: + x-kubernetes-map-type: atomic + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + properties: items: + items: + description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + properties: + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by scope of the resources. + items: + description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + properties: + operator: + description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector applies to. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + items: + description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + type: string + type: array + type: object + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + enum: + - Tenant + - Namespace type: string - type: array - allowedRegex: - type: string - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. - items: + type: object + runtimeClasses: + description: Specifies the allowed RuntimeClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - resourceQuotas: - description: Specifies a list of ResourceQuota resources assigned - to the Tenant. The assigned values are inherited by any namespace - created in the Tenant. The Capsule operator aggregates ResourceQuota - at Tenant level, so that the hard quota is never crossed for the - given Tenant. This permits the Tenant owner to consume resources - in the Tenant regardless of the namespace. Optional. - properties: - items: - items: - description: ResourceQuotaSpec defines the desired hard limits - to enforce for Quota. + type: object + x-kubernetes-map-type: atomic + serviceOptions: + description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. properties: - hard: + annotations: additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for - each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' - type: object - scopeSelector: - description: scopeSelector is also a collection of filters - like scopes that must match each object tracked by a quota - but expressed using ScopeSelectorOperator in combination - with possible values. For a resource to match, both scopes - AND scopeSelector (if specified in spec), must be matched. - properties: - matchExpressions: - description: A list of scope selector requirements by - scope of the resources. - items: - description: A scoped-resource selector requirement - is a selector that contains values, a scope name, - and an operator that relates the scope name and - values. - properties: - operator: - description: Represents a scope's relationship - to a set of values. Valid operators are In, - NotIn, Exists, DoesNotExist. - type: string - scopeName: - description: The name of the scope that the selector - applies to. - type: string - values: - description: An array of string values. If the - operator is In or NotIn, the values array must - be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is - replaced during a strategic merge patch. - items: - type: string - type: array - required: - - operator - - scopeName - type: object - type: array + type: string type: object - x-kubernetes-map-type: atomic - scopes: - description: A collection of filters that must match each - object tracked by a quota. If not specified, the quota - matches all objects. - items: - description: A ResourceQuotaScope defines a filter that - must match each object tracked by a quota + labels: + additionalProperties: type: string - type: array + type: object type: object - type: array - scope: - default: Tenant - description: Define if the Resource Budget should compute resource - across all Namespaces in the Tenant or individually per cluster. - Default is Tenant - enum: - - Tenant - - Namespace - type: string - type: object - runtimeClasses: - description: Specifies the allowed RuntimeClasses assigned to the - Tenant. Capsule assures that all Pods resources created in the Tenant - can use only one of the allowed RuntimeClasses. Optional. - properties: - allowed: - items: - type: string - type: array - allowedRegex: - type: string - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. + allowedServices: + description: Block or deny certain type of Services. Optional. properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. + externalName: + default: true + description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + properties: + allowed: items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ type: string type: array required: - - key - - operator + - allowed type: object - type: array - matchLabels: - additionalProperties: + type: object + storageClasses: + description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - serviceOptions: - description: Specifies options for the Service, such as additional - metadata or block of certain type of Services. Optional. - properties: - additionalMetadata: - description: Specifies additional labels and annotations the Capsule - operator places on any Service resource in the Tenant. Optional. - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - allowedServices: - description: Block or deny certain type of Services. Optional. - properties: - externalName: - default: true - description: Specifies if ExternalName service type resources - are allowed for the Tenant. Default is true. Optional. - type: boolean - loadBalancer: - default: true - description: Specifies if LoadBalancer service type resources - are allowed for the Tenant. Default is true. Optional. - type: boolean - nodePort: - default: true - description: Specifies if NodePort service type resources - are allowed for the Tenant. Default is true. Optional. - type: boolean - type: object - externalIPs: - description: Specifies the external IPs that can be used in Services - with type ClusterIP. An empty list means no IPs are allowed. - Optional. - properties: - allowed: - items: - pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ - type: string - type: array - required: - - allowed - type: object - type: object - storageClasses: - description: Specifies the allowed StorageClasses assigned to the - Tenant. Capsule assures that all PersistentVolumeClaim resources - created in the Tenant can use only one of the allowed StorageClasses. - Optional. - properties: - allowed: - items: + default: type: string - type: array - allowedRegex: - type: string - matchExpressions: - description: matchExpressions is a list of label selector requirements. - The requirements are ANDed. - items: - description: A label selector requirement is a selector that - contains values, a key, and an operator that relates the key - and values. - properties: - key: - description: key is the label key that the selector applies - to. - type: string - operator: - description: operator represents a key's relationship to - a set of values. Valid operators are In, NotIn, Exists - and DoesNotExist. - type: string - values: - description: values is an array of string values. If the - operator is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during a strategic - merge patch. - items: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. type: string - type: array - required: - - key - - operator + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single - {key,value} in the matchLabels map is equivalent to an element - of matchExpressions, whose key field is "key", the operator - is "In", and the values array contains only "value". The requirements - are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - owners - type: object - status: - description: Returns the observed state of the Tenant. - properties: - namespaces: - description: List of namespaces assigned to the Tenant. - items: + type: object + x-kubernetes-map-type: atomic + required: + - owners + type: object + status: + description: Returns the observed state of the Tenant. + properties: + namespaces: + description: List of namespaces assigned to the Tenant. + items: + type: string + type: array + size: + description: How many namespaces are assigned to the Tenant. + type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + enum: + - Cordoned + - Active type: string - type: array - size: - description: How many namespaces are assigned to the Tenant. - type: integer - state: - default: Active - description: The operational state of the Tenant. Possible values - are "Active", "Cordoned". - enum: - - Cordoned - - Active - type: string - required: - - size - - state - type: object - type: object - served: true - storage: true - subresources: - status: {} + required: + - size + - state + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/capsule/templates/mutatingwebhookconfiguration.yaml b/charts/capsule/templates/mutatingwebhookconfiguration.yaml index 9997e702..0d396ee8 100644 --- a/charts/capsule/templates/mutatingwebhookconfiguration.yaml +++ b/charts/capsule/templates/mutatingwebhookconfiguration.yaml @@ -12,6 +12,86 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} webhooks: +{{- with .Values.webhooks.defaults.pods }} +- admissionReviewVersions: + - v1 + clientConfig: + {{- if not $.Values.certManager.generateCertificates }} + caBundle: Cg== + {{- end }} + service: + name: {{ include "capsule.fullname" $ }}-webhook-service + namespace: {{ $.Release.Namespace }} + path: /defaults + failurePolicy: {{ .failurePolicy }} + name: pod.defaults.capsule.clastix.io + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - pods + namespaceSelector: + {{- toYaml .namespaceSelector | nindent 4}} + sideEffects: None +{{- end }} +{{- with .Values.webhooks.defaults.pvc }} +- admissionReviewVersions: + - v1 + clientConfig: + {{- if not $.Values.certManager.generateCertificates }} + caBundle: Cg== + {{- end }} + service: + name: {{ include "capsule.fullname" $ }}-webhook-service + namespace: {{ $.Release.Namespace }} + path: /defaults + failurePolicy: {{ .failurePolicy }} + name: storage.defaults.capsule.clastix.io + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - persistentvolumeclaims + namespaceSelector: + {{- toYaml .namespaceSelector | nindent 4}} + sideEffects: None +{{- end }} +{{- with .Values.webhooks.defaults.ingress }} +- admissionReviewVersions: + - v1 + clientConfig: + {{- if not $.Values.certManager.generateCertificates }} + caBundle: Cg== + {{- end }} + service: + name: {{ include "capsule.fullname" $ }}-webhook-service + namespace: {{ $.Release.Namespace }} + path: /defaults + failurePolicy: {{ .failurePolicy }} + name: ingress.defaults.capsule.clastix.io + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1beta1 + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + namespaceSelector: + {{- toYaml .namespaceSelector | nindent 4}} + sideEffects: None +{{- end }} - admissionReviewVersions: - v1 - v1beta1 diff --git a/charts/capsule/templates/validatingwebhookconfiguration.yaml b/charts/capsule/templates/validatingwebhookconfiguration.yaml index c20b22a7..80b80c03 100644 --- a/charts/capsule/templates/validatingwebhookconfiguration.yaml +++ b/charts/capsule/templates/validatingwebhookconfiguration.yaml @@ -145,6 +145,34 @@ webhooks: clientConfig: {{- if not .Values.certManager.generateCertificates }} caBundle: Cg== +{{- end }} + service: + name: {{ include "capsule.fullname" . }}-webhook-service + namespace: {{ .Release.Namespace }} + path: /nodes + port: 443 + failurePolicy: {{ .Values.webhooks.nodes.failurePolicy }} + name: nodes.capsule.clastix.io + matchPolicy: Exact + namespaceSelector: {} + objectSelector: {} + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - UPDATE + resources: + - nodes + sideEffects: None + timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }} +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: +{{- if not .Values.certManager.generateCertificates }} + caBundle: Cg== {{- end }} service: name: {{ include "capsule.fullname" . }}-webhook-service @@ -260,31 +288,3 @@ webhooks: scope: '*' sideEffects: None timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }} -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: -{{- if not .Values.certManager.generateCertificates }} - caBundle: Cg== -{{- end }} - service: - name: {{ include "capsule.fullname" . }}-webhook-service - namespace: {{ .Release.Namespace }} - path: /nodes - port: 443 - failurePolicy: {{ .Values.webhooks.nodes.failurePolicy }} - name: nodes.capsule.clastix.io - matchPolicy: Exact - namespaceSelector: {} - objectSelector: {} - rules: - - apiGroups: - - "" - apiVersions: - - v1 - operations: - - UPDATE - resources: - - nodes - sideEffects: None - timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }} diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index 8885801a..b3e89642 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -172,6 +172,26 @@ webhooks: operator: Exists nodes: failurePolicy: Fail + defaults: + ingress: + failurePolicy: Fail + namespaceSelector: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists + pvc: + failurePolicy: Fail + namespaceSelector: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists + pods: + failurePolicy: Fail + namespaceSelector: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists + # -- Timeout in seconds for mutating webhooks mutatingWebhooksTimeoutSeconds: 30 From 7784e8df50419d38440e60325d5c1f191e7f3256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 5 Jan 2023 17:25:58 +0100 Subject: [PATCH 064/153] docs: add defaults handler for storage, ingress, and priority classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- docs/content/general/crds-apis.md | 33 ++++- docs/content/general/tutorial.md | 197 +++++++++++++++++++++++++++--- 2 files changed, 210 insertions(+), 20 deletions(-) diff --git a/docs/content/general/crds-apis.md b/docs/content/general/crds-apis.md index 999b41f2..73cb38e8 100644 --- a/docs/content/general/crds-apis.md +++ b/docs/content/general/crds-apis.md @@ -2974,7 +2974,7 @@ TenantSpec defines the desired state of Tenant. priorityClasses object - Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional.
+ Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. A default value can be specified, and all the Pod resources created will inherit the declared class. Optional.
false @@ -3002,7 +3002,7 @@ TenantSpec defines the desired state of Tenant. storageClasses object - Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
+ Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class. Optional.
false @@ -3234,7 +3234,7 @@ Specifies options for the Ingress resources, such as allowed hostnames and Ingre allowedClasses object - Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
+ Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. A default value can be specified, and all the Ingress resources created will inherit the declared class. Optional.
false @@ -3266,7 +3266,7 @@ Specifies options for the Ingress resources, such as allowed hostnames and Ingre -Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. +Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. A default value can be specified, and all the Ingress resources created will inherit the declared class. Optional. @@ -3291,6 +3291,13 @@ Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures tha
+ + + + + @@ -4394,7 +4401,7 @@ NetworkPolicyPort describes a port to allow traffic on -Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. +Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. A default value can be specified, and all the Pod resources created will inherit the declared class. Optional.
false
defaultstring +
+
false
matchExpressions []object
@@ -4419,6 +4426,13 @@ Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures th
+ + + + + @@ -4855,7 +4869,7 @@ Specifies the external IPs that can be used in Services with type ClusterIP. An -Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. +Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class. Optional.
false
defaultstring +
+
false
matchExpressions []object
@@ -4880,6 +4894,13 @@ Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures tha
+ + + + + diff --git a/docs/content/general/tutorial.md b/docs/content/general/tutorial.md index 08d28d0a..7132252a 100644 --- a/docs/content/general/tutorial.md +++ b/docs/content/general/tutorial.md @@ -819,7 +819,7 @@ To prevent misuses of Pod Priority Class, Bill, the cluster admin, can enforce t ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -829,22 +829,73 @@ spec: kind: User priorityClasses: allowed: - - default + - custom allowedRegex: "^tier-.*$" + selector: + matchLabels: + env: "production" EOF ``` With the said Tenant specification, Alice can create a Pod resource if `spec.priorityClassName` equals to: -- `default` +- `custom` - `tier-gold`, `tier-silver`, or `tier-bronze`, since these compile the allowed regex. +- Any PriorityClass which has the label `env` with the value `production` If a Pod is going to use a non-allowed _Priority Class_, it will be rejected by the Validation Webhook enforcing it. +### Assign Pod Priority Class as tenant default + +It's possible to assign each tenant a PriorityClass which will be used, if no PriorityClass is set on pod basis: + +```yaml +kubectl apply -f - << EOF +apiVersion: capsule.clastix.io/v1beta2 +kind: Tenant +metadata: + name: oil +spec: + owners: + - name: alice + kind: User + priorityClasses: + allowed: + - custom + default: "tenant-default" + allowedRegex: "^tier-.*$" + selector: + matchLabels: + env: "production" +EOF +``` + +Here's how the new PriorityClass could look like + +```yaml +kubectl apply -f - << EOF +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: tenant-default +value: 1313 +preemptionPolicy: Never +globalDefault: false +description: "This is the default PriorityClass for the oil-tenant" +EOF +``` + +If a Pod has no value for `spec.priorityClassName`, the default value for PriorityClass (`tenant-default`) will be used. + +> This feature allows specifying a custom default value on a Tenant basis, bypassing the global cluster default (`globalDefault=true`) that acts only at the cluster level. + +**Note**: This feature supports type `PriorityClass` only on API version `scheduling.k8s.io/v1` + ## Assign Pod Runtime Classes -Pods can be assigned different runtime classes. With the assigned runtime you can control Container Runtime Interface (CRI) is used for each pod. See [Kubernetes documentation](https://kubernetes.io/docs/concepts/containers/runtime-class/). +Pods can be assigned different runtime classes. With the assigned runtime you can control Container Runtime Interface (CRI) is used for each pod. +See [Kubernetes documentation](https://kubernetes.io/docs/concepts/containers/runtime-class/) for more information. To prevent misuses of Pod Runtime Classes, Bill, the cluster admin, can enforce the allowed Pod Runtime Class at tenant level: @@ -868,15 +919,14 @@ spec: EOF ``` -With the said Tenant specification, Alice can create a Pod resource if `spec.RuntimeClasses` equals to: +With the said Tenant specification, Alice can create a Pod resource if `spec.runtimeClassName` equals to: - `legacy` -- `hardened-crio` or `hardened-containerd`, since these compile the allowed regex. -- Any RuntimeClass which has the label `env` with the value `production` +- e.g.: `hardened-crio` or `hardened-containerd`, since these compile the allowed regex (`^hardened-.*$"`). +- any RuntimeClass which has the label `env` with the value `production` If a Pod is going to use a non-allowed _Runtime Class_, it will be rejected by the Validation Webhook enforcing it. - ## Assign Nodes Pool Bill, the cluster admin, can dedicate a pool of worker nodes to the `oil` tenant, to isolate the tenant applications from other noisy neighbors. @@ -946,7 +996,7 @@ Bill can assign a set of dedicated Ingress Classes to the `oil` tenant to force ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -957,12 +1007,21 @@ spec: ingressOptions: allowedClasses: allowed: - - default + - legacy allowedRegex: ^\w+-lb$ + selector: + matchLabels: + env: "production" EOF ``` -Capsule assures that all Ingresses created in the tenant can use only one of the valid Ingress Classes. +With the said Tenant specification, Alice can create a Ingress resource if `spec.ingressClassName` or `metadata.annotations."kubernetes.io/ingress.class"` equals to: + +- `legacy` +- eg. `haproxy-lb` or `nginx-lb`, since these compile the allowed regex (`^\w+-lb$`). +- Any IngressClass which has the label `env` with the value `production` + +If an Ingress is going to use a non-allowed _IngressClass_, it will be rejected by the Validation Webhook enforcing it. Alice can create an Ingress using only an allowed Ingress Class: @@ -974,7 +1033,7 @@ metadata: name: nginx namespace: oil-production annotations: - kubernetes.io/ingress.class: default + kubernetes.io/ingress.class: legacy spec: rules: - host: oil.acmecorp.com @@ -989,6 +1048,58 @@ EOF Any attempt of Alice to use a non-valid Ingress Class, or missing it, is denied by the Validation Webhook enforcing it. +### Assign Ingress Class as tenant default + +It's possible to assign each tenant an Ingress Class which will be used, if a class is not set on ingress basis: + +```yaml +kubectl apply -f - << EOF +apiVersion: capsule.clastix.io/v1beta2 +kind: Tenant +metadata: + name: oil +spec: + owners: + - name: alice + kind: User + ingressOptions: + allowedClasses: + allowed: + - legacy + default: "tenant-default" + allowedRegex: ^\w+-lb$ + selector: + matchLabels: + env: "production" +EOF +``` + +Here's how the Tenant default IngressClass could look like: + +```yaml +kubectl apply -f - << EOF +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + labels: + app.kubernetes.io/component: controller + name: tenant-default + annotations: + ingressclass.kubernetes.io/is-default-class: "false" +spec: + controller: k8s.io/customer-nginx +EOF +``` + +If an Ingress has no value for `spec.ingressClassName` or `metadata.annotations."kubernetes.io/ingress.class"`, the `tenant-default` IngressClass is automatically applied to the Ingress resource. + +> This feature allows specifying a custom default value on a Tenant basis, bypassing the global cluster default (with the annotation `metadata.annotations.ingressclass.kubernetes.io/is-default-class=true`) that acts only at the cluster level. +> +> More information: [Default IngressClass](https://kubernetes.io/docs/concepts/services-networking/ingress/#default-ingress-class) + +**Note**: This feature is offered only by API type `IngressClass` in group `networking.k8s.io` version `v1`. +However, resource `Ingress` is supported in `networking.k8s.io/v1` and `networking.k8s.io/v1beta1` + ## Assign Ingress Hostnames Bill can control ingress hostnames in the `oil` tenant to force the applications to be published only using the given hostname or set of hostnames: @@ -1124,7 +1235,7 @@ Persistent storage infrastructure is provided to tenants. Different types of sto ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1137,9 +1248,18 @@ spec: - ceph-rbd - ceph-nfs allowedRegex: "^ceph-.*$" + selector: + matchLabels: + env: "production" EOF ``` +With the said Tenant specification, Alice can create a Persistent Volume Claims if `spec.storageClassName` equals to: + +- `ceph-rbd` or `ceph-nfs` +- eg. `ceph-hdd` or `ceph-ssd`, since these compile the allowed regex (`^ceph-.*$`). +- Any IngressClass which has the label `env` with the value `production` + Capsule assures that all Persistent Volume Claims created by Alice will use only one of the valid storage classes: ```yaml @@ -1159,7 +1279,56 @@ spec: EOF ``` -Any attempt of Alice to use a non-valid Storage Class, or missing it, is denied by the Validation Webhook enforcing it. +If a Persistent Volume Claim is going to use a non-allowed _Storage Class_, it will be rejected by the Validation Webhook enforcing it. + +### Assign Storage Class as tenant default + +It's possible to assign each tenant a StorageClass which will be used, if no value is set on Persistent Volume Claim basis: + +```yaml +kubectl apply -f - << EOF +apiVersion: capsule.clastix.io/v1beta2 +kind: Tenant +metadata: + name: oil +spec: + owners: + - name: alice + kind: User + storageClasses: + default: "tenant-default" + allowed: + - ceph-rbd + - ceph-nfs + allowedRegex: "^ceph-.*$" + selector: + matchLabels: + env: "production" +EOF +``` + +Here's how the new Storage Class could look like + +```yaml +kubectl apply -f - << EOF +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: tenant-default + annotations: + storageclass.kubernetes.io/is-default-class: "false" +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: WaitForFirstConsumer +EOF +``` + +If a Persistent Volume Claim has no value for `spec.storageClassName` the `tenant-default` value will be used on new Persistent Volume Claim resources. + +> This feature allows specifying a custom default value on a Tenant basis, bypassing the global cluster default (`.metadata.annotations.storageclass.kubernetes.io/is-default-class=true`) that acts only at the cluster level. +> +> See the [Default Storage Class](https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/) section on Kubernetes documentation. + +**Note**: This feature supports type `StorageClass` only on API version `storage.k8s.io/v1` ## Assign Network Policies Kubernetes network policies control network traffic between namespaces and between pods in the same namespace. Bill, the cluster admin, can enforce network traffic isolation between different tenants while leaving to Alice, the tenant owner, the freedom to set isolation between namespaces in the same tenant or even between pods in the same namespace. From 68c86a6509390be542ca564d066ba7da48d8a538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 5 Jan 2023 17:26:47 +0100 Subject: [PATCH 065/153] test(e2e): add defaults handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- e2e/ingress_class_extensions_test.go | 17 +- e2e/ingress_class_networking_test.go | 433 +++++++++++++++++++++++++-- e2e/owner_webhooks_test.go | 12 +- e2e/pod_priority_class_test.go | 370 +++++++++++++++++++---- e2e/storage_class_test.go | 319 ++++++++++++++++++-- 5 files changed, 1029 insertions(+), 122 deletions(-) diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index e17b205e..71b912be 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -35,13 +35,18 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }, }, IngressOptions: capsulev1beta2.IngressOptions{ - AllowedClasses: &api.SelectorAllowedListSpec{ - AllowedListSpec: api.AllowedListSpec{ - Exact: []string{ - "nginx", - "haproxy", + AllowedClasses: &api.DefaultAllowedListSpec{ + Default: "tenant-default", + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{"nginx", "haproxy"}, + Regex: "^oil-.*$", + }, + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "customers", + }, }, - Regex: "^oil-.*$", }, }, }, diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index 0ecdd67b..99e19118 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -9,52 +9,151 @@ import ( "context" "errors" "fmt" + "strconv" + "strings" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" networkingv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1", func() { - tnt := &capsulev1beta2.Tenant{ + tntNoDefault := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ - Name: "ingress-class-networking-v1", + Name: "ic-selector-networking-v1", }, Spec: capsulev1beta2.TenantSpec{ Owners: []capsulev1beta2.OwnerSpec{ { - Name: "ingress", + Name: "ingress-selector", Kind: "User", }, }, IngressOptions: capsulev1beta2.IngressOptions{ - AllowedClasses: &api.SelectorAllowedListSpec{ - AllowedListSpec: api.AllowedListSpec{ - Exact: []string{ - "nginx", - "haproxy", + AllowedClasses: &api.DefaultAllowedListSpec{ + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{"nginx", "haproxy"}, + Regex: "^oil-.*$", + }, + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "customers", + }, + }, + }, + }, + }, + }, + } + + tntWithDefault := &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ic-default-networking-v1", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: []capsulev1beta2.OwnerSpec{ + { + Name: "ingress-default", + Kind: "User", + }, + }, + IngressOptions: capsulev1beta2.IngressOptions{ + AllowedClasses: &api.DefaultAllowedListSpec{ + Default: "tenant-default", + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": "tenant-default", + }, }, - Regex: "^oil-.*$", }, }, }, }, } + tenantDefault := networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant-default", + Labels: map[string]string{ + "name": "tenant-default", + "env": "e2e", + }, + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "k8s.io/ingress-nginx", + }, + } + + globalDefault := networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global-default", + Labels: map[string]string{ + "name": "global-default", + "env": "customers", + }, + Annotations: map[string]string{ + "ingressclass.kubernetes.io/is-default-class": "true", + }, + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "k8s.io/ingress-nginx", + }, + } + + disallowedGlobalDefault := networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "disallowed", + Labels: map[string]string{ + "name": "disallowed-global-default", + "env": "e2e", + }, + Annotations: map[string]string{ + "ingressclass.kubernetes.io/is-default-class": "true", + }, + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "k8s.io/ingress-nginx", + }, + } + JustBeforeEach(func() { - EventuallyCreation(func() error { - tnt.ResourceVersion = "" - return k8sClient.Create(context.TODO(), tnt) - }).Should(Succeed()) + for _, tnt := range []*capsulev1beta2.Tenant{tntWithDefault, tntNoDefault} { + EventuallyCreation(func() error { + tnt.ResourceVersion = "" + + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + } }) + JustAfterEach(func() { - Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + for _, tnt := range []*capsulev1beta2.Tenant{tntWithDefault, tntNoDefault} { + Eventually(func() error { + return k8sClient.Delete(context.TODO(), tnt) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + } + + Eventually(func() (err error) { + req, _ := labels.NewRequirement("env", selection.Exists, nil) + + return k8sClient.DeleteAllOf(context.TODO(), &networkingv1.IngressClass{}, &client.DeleteAllOfOptions{ + ListOptions: client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*req), + }, + }) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) It("should block a non allowed class", func() { @@ -66,10 +165,10 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0]) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("non-specifying at all", func() { Eventually(func() (err error) { @@ -149,12 +248,12 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0]) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) - for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact { + for _, c := range tntNoDefault.Spec.IngressOptions.AllowedClasses.Exact { Eventually(func() (err error) { i := &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ @@ -189,12 +288,12 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0]) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) - for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact { + for _, c := range tntNoDefault.Spec.IngressOptions.AllowedClasses.Exact { Eventually(func() (err error) { i := &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ @@ -227,11 +326,11 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0]) ingressClass := "oil-ingress" - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) Eventually(func() (err error) { i := &networkingv1.Ingress{ @@ -266,11 +365,11 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0]) ingressClass := "oil-haproxy" - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) Eventually(func() (err error) { i := &networkingv1.Ingress{ @@ -293,4 +392,276 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" return }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) + + It("should allow enabled Ingress by selector using the deprecated annotation", func() { + if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { + missingAPIError := &meta.NoKindMatchError{} + if errors.As(err, &missingAPIError) { + Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) + } + } + + for i, sc := range []string{"customer-nginx", "customer-haproxy"} { + ingressClass := strings.Join([]string{sc, "-", strconv.Itoa(i)}, "") + class := &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: ingressClass, + Labels: map[string]string{ + "name": ingressClass, + "env": "customers", + }, + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "k8s.io/ingress-nginx", + }, + } + Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) + + i := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("allowed-%s", ingressClass), + Annotations: map[string]string{ + "kubernetes.io/ingress.class": ingressClass, + }, + }, + Spec: networkingv1.IngressSpec{ + DefaultBackend: &networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "foo", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + } + + ns := NewNamespace("") + cs := ownerClient(tntNoDefault.Spec.Owners[0]) + + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + EventuallyCreation(func() error { + _, err := cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), i, metav1.CreateOptions{}) + return err + }).Should(Succeed()) + } + }) + + It("should allow enabled Ingress by selector using the ingressClassName field", func() { + if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { + missingAPIError := &meta.NoKindMatchError{} + if errors.As(err, &missingAPIError) { + Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) + } + } + + for i, sc := range []string{"customer-nginx", "customer-haproxy"} { + ingressClass := strings.Join([]string{sc, "-", strconv.Itoa(i)}, "") + class := &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: ingressClass, + Labels: map[string]string{ + "name": ingressClass, + "env": "customers", + }, + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "k8s.io/ingress-nginx", + }, + } + Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) + + i := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("allowed-%s", ingressClass), + }, + Spec: networkingv1.IngressSpec{ + IngressClassName: &ingressClass, + DefaultBackend: &networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "foo", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + } + + ns := NewNamespace("") + cs := ownerClient(tntNoDefault.Spec.Owners[0]) + + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + EventuallyCreation(func() error { + _, err := cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), i, metav1.CreateOptions{}) + return err + }).Should(Succeed()) + } + }) + + It("should mutate to default tenant IngressClass (class not does not exist)", func() { + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + i := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e2e-default-ingress", + Namespace: ns.GetName(), + }, + Spec: networkingv1.IngressSpec{ + DefaultBackend: &networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "foo", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), i) + }).Should(Succeed()) + + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: i.GetName(), Namespace: ns.GetName()}, i)) + Expect(*i.Spec.IngressClassName).To(Equal("tenant-default")) + }) + + It("should mutate to default tenant IngressClass (class exists)", func() { + if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { + missingAPIError := &meta.NoKindMatchError{} + if errors.As(err, &missingAPIError) { + Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) + } + } + + class := tenantDefault + Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed()) + + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + i := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e2e-default-ingress", + Namespace: ns.GetName(), + }, + Spec: networkingv1.IngressSpec{ + DefaultBackend: &networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "foo", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), i) + }).Should(Succeed()) + + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: i.GetName(), Namespace: ns.GetName()}, i)) + Expect(*i.Spec.IngressClassName).To(Equal(class.GetName())) + }) + + It("shoult mutate to default tenant IngressClass although the cluster global one is not allowed", func() { + if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { + missingAPIError := &meta.NoKindMatchError{} + if errors.As(err, &missingAPIError) { + Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) + } + } + + class := tenantDefault + global := disallowedGlobalDefault + + Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed()) + Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed()) + + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + i := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e2e-default-global-ingress", + Namespace: ns.GetName(), + }, + Spec: networkingv1.IngressSpec{ + DefaultBackend: &networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "foo", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), i) + }).Should(Succeed()) + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: i.GetName(), Namespace: ns.GetName()}, i)) + Expect(*i.Spec.IngressClassName).To(Equal(class.GetName())) + // Run Patch To verify same happens on Update + i.Spec.IngressClassName = nil + Expect(k8sClient.Update(context.Background(), i)).Should(Succeed()) + Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: i.GetName(), Namespace: ns.GetName()}, i)) + Expect(*i.Spec.IngressClassName).To(Equal(class.GetName())) + }) + + It("should mutate to default tenant IngressClass although the cluster global one is allowed", func() { + if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { + missingAPIError := &meta.NoKindMatchError{} + if errors.As(err, &missingAPIError) { + Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) + } + } + + class := tenantDefault + global := globalDefault + + Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed()) + Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed()) + + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + i := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e2e-default-global-ingress", + Namespace: ns.GetName(), + }, + Spec: networkingv1.IngressSpec{ + DefaultBackend: &networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "foo", + Port: networkingv1.ServiceBackendPort{ + Number: 8080, + }, + }, + }, + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), i) + }).Should(Succeed()) + Expect(*i.Spec.IngressClassName).To(Equal(class.GetName())) + // Run Patch To verify same happens on Update + i.Spec.IngressClassName = nil + Expect(k8sClient.Update(context.Background(), i)).Should(Succeed()) + Expect(*i.Spec.IngressClassName).To(Equal(class.GetName())) + }) }) diff --git a/e2e/owner_webhooks_test.go b/e2e/owner_webhooks_test.go index b20772b6..57a990b5 100644 --- a/e2e/owner_webhooks_test.go +++ b/e2e/owner_webhooks_test.go @@ -33,11 +33,13 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() { Kind: "User", }, }, - StorageClasses: &api.SelectorAllowedListSpec{ - AllowedListSpec: api.AllowedListSpec{ - Exact: []string{ - "cephfs", - "glusterfs", + StorageClasses: &api.DefaultAllowedListSpec{ + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{ + "cephfs", + "glusterfs", + }, }, }, }, diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index f2251f79..d4737650 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -13,52 +13,135 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/scheduling/v1" + schedulingv1 "k8s.io/api/scheduling/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("enforcing a Priority Class", func() { - tnt := &capsulev1beta2.Tenant{ + tntWithDefaults := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ - Name: "priority-class", + Name: "priority-class-defaults", }, Spec: capsulev1beta2.TenantSpec{ Owners: capsulev1beta2.OwnerListSpec{ { - Name: "george", + Name: "paul", Kind: "User", }, }, - PriorityClasses: &api.SelectorAllowedListSpec{ - AllowedListSpec: api.AllowedListSpec{ - Exact: []string{"gold"}, - Regex: "pc\\-\\w+", + PriorityClasses: &api.DefaultAllowedListSpec{ + Default: "tenant-default", + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "customer", + }, + }, + }, + }, + }, + } + + tntNoDefaults := &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "priority-class-no-defaults", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ + { + Name: "george", + Kind: "User", }, - LabelSelector: metav1.LabelSelector{ - MatchLabels: map[string]string{ - "env": "customers", + }, + PriorityClasses: &api.DefaultAllowedListSpec{ + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{"gold"}, + Regex: "pc\\-\\w+", + }, + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "customer", + }, }, }, }, }, } + pcTenantPreemption := corev1.PreemptionPolicy("PreemptLowerPriority") + tenantDefault := schedulingv1.PriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant-default", + Labels: map[string]string{ + "env": "e2e", + }, + }, + Description: "tenant default priorityclass", + Value: 1212, + PreemptionPolicy: &pcTenantPreemption, + GlobalDefault: false, + } + + globalDefault := schedulingv1.PriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global-default", + Labels: map[string]string{ + "env": "customer", + }, + }, + Description: "global default priorityclass", + Value: 100000, + GlobalDefault: true, + } + + disallowedGlobalDefault := schedulingv1.PriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "disallowed-global-default", + Labels: map[string]string{ + "env": "e2e", + }, + }, + Description: "global default priorityclass", + Value: 100000, + GlobalDefault: true, + } + JustBeforeEach(func() { - EventuallyCreation(func() error { - tnt.ResourceVersion = "" - return k8sClient.Create(context.TODO(), tnt) - }).Should(Succeed()) + for _, tnt := range []*capsulev1beta2.Tenant{tntWithDefaults, tntNoDefaults} { + EventuallyCreation(func() error { + tnt.ResourceVersion = "" + + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + } }) + JustAfterEach(func() { - Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + for _, tnt := range []*capsulev1beta2.Tenant{tntWithDefaults, tntNoDefaults} { + Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + } + + Eventually(func() (err error) { + req, _ := labels.NewRequirement("env", selection.Exists, nil) + + return k8sClient.DeleteAllOf(context.TODO(), &schedulingv1.PriorityClass{}, &client.DeleteAllOfOptions{ + ListOptions: client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*req), + }, + }) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) It("should block non allowed Priority Class", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -75,29 +158,71 @@ var _ = Describe("enforcing a Priority Class", func() { }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0]) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) return err }).ShouldNot(Succeed()) }) + It("should block non matching selector match", func() { + for i, pc := range []string{"internal-bronze", "internal-silver", "internal-gold"} { + priorityName := strings.Join([]string{pc, "-", strconv.Itoa(i)}, "") + class := &schedulingv1.PriorityClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: priorityName, + Labels: map[string]string{ + "env": "internal", + }, + }, + Description: "fake PriorityClass for e2e", + Value: int32(10000 * (i + 2)), + } + Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: pc, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + PriorityClassName: class.GetName(), + }, + } + + ns := NewNamespace("") + cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + return err + }).ShouldNot(Succeed()) + } + }) + It("should allow exact match", func() { - pc := &v1.PriorityClass{ + pc := &schedulingv1.PriorityClass{ ObjectMeta: metav1.ObjectMeta{ Name: "gold", + Labels: map[string]string{ + "env": "e2e", + }, }, Description: "fake PriorityClass for e2e", Value: 10000, } Expect(k8sClient.Create(context.TODO(), pc)).Should(Succeed()) - defer func() { - Expect(k8sClient.Delete(context.TODO(), pc)).Should(Succeed()) - }() - ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -114,7 +239,7 @@ var _ = Describe("enforcing a Priority Class", func() { }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0]) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) return err @@ -123,13 +248,15 @@ var _ = Describe("enforcing a Priority Class", func() { It("should allow regex match", func() { ns := NewNamespace("") - - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) for i, pc := range []string{"pc-bronze", "pc-silver", "pc-gold"} { - class := &v1.PriorityClass{ + class := &schedulingv1.PriorityClass{ ObjectMeta: metav1.ObjectMeta{ Name: pc, + Labels: map[string]string{ + "env": "e2e", + }, }, Description: "fake PriorityClass for e2e", Value: int32(10000 * (i + 2)), @@ -137,45 +264,36 @@ var _ = Describe("enforcing a Priority Class", func() { Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: pc, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "container", - Image: "quay.io/google-containers/pause-amd64:3.0", + EventuallyCreation(func() error { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: pc, + Namespace: ns.GetName(), + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, }, + PriorityClassName: class.GetName(), }, - PriorityClassName: class.GetName(), - }, - } - - cs := ownerClient(tnt.Spec.Owners[0]) + } - EventuallyCreation(func() error { - _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) - return err + return k8sClient.Create(context.Background(), pod) }).Should(Succeed()) - - Expect(k8sClient.Delete(context.TODO(), class)).Should(Succeed()) } }) It("should allow selector match", func() { - ns := NewNamespace("priority-selector-match") - - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - for i, pc := range []string{"customer-bronze", "customer-silver", "customer-gold"} { priorityName := strings.Join([]string{pc, "-", strconv.Itoa(i)}, "") - class := &v1.PriorityClass{ + class := &schedulingv1.PriorityClass{ ObjectMeta: metav1.ObjectMeta{ - Name: pc, + Name: priorityName, Labels: map[string]string{ - "name": priorityName, - "env": "customers", + "env": "customer", }, }, Description: "fake PriorityClass for e2e", @@ -198,15 +316,153 @@ var _ = Describe("enforcing a Priority Class", func() { }, } - cs := ownerClient(tnt.Spec.Owners[0]) + ns := NewNamespace("") + cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) return err }).Should(Succeed()) + } + }) + + It("fail if default tenant PriorityClass is absent", func() { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant-default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + }, + } + + ns := NewNamespace("") + cs := ownerClient(tntWithDefaults.Spec.Owners[0]) + + NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + EventuallyCreation(func() error { + _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + + return err + }).ShouldNot(Succeed()) + + Expect(k8sClient.Delete(context.TODO(), ns)).Should(Succeed()) + }) + + It("should mutate to default tenant PriorityClass", func() { + class := tenantDefault.DeepCopy() + class.SetResourceVersion("") + Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) + + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant-default-present", + Namespace: ns.GetName(), + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), &pod) + }).Should(Succeed()) + // Check if correct mutated + Expect(pod.Spec.PriorityClassName).To(Equal(class.GetName())) + Expect(pod.Spec.Priority).To(Equal(&class.Value)) + Expect(pod.Spec.PreemptionPolicy).To(Equal(class.PreemptionPolicy)) + }) + + It("should mutate to default tenant PriorityClass although the cluster global one is not allowed", func() { + class := tenantDefault.DeepCopy() + class.SetResourceVersion("") + + global := disallowedGlobalDefault.DeepCopy() + global.SetResourceVersion("") + + Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) + Expect(k8sClient.Create(context.TODO(), global)).Should(Succeed()) + + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) - Expect(k8sClient.Delete(context.TODO(), class)).Should(Succeed()) + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant-default-global-default", + Namespace: ns.GetName(), + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + }, } + + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), pod) + }).Should(Succeed()) + // Check if correct applied + Expect(pod.Spec.PriorityClassName).To(Equal(class.GetName())) + Expect(pod.Spec.Priority).To(Equal(&class.Value)) + Expect(pod.Spec.PreemptionPolicy).To(Equal(class.PreemptionPolicy)) }) + It("should mutate to default tenant PriorityClass although the cluster global one is allowed", func() { + class := tenantDefault.DeepCopy() + class.SetResourceVersion("") + Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) + + global := globalDefault.DeepCopy() + global.SetResourceVersion("") + Expect(k8sClient.Create(context.TODO(), global)).Should(Succeed()) + + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant-default-allowed", + Namespace: ns.GetName(), + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "quay.io/google-containers/pause-amd64:3.0", + }, + }, + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), pod) + }).Should(Succeed()) + // Check if correctly applied + Expect(pod.Spec.PriorityClassName).To(Equal(class.GetName())) + Expect(*pod.Spec.Priority).To(Equal(class.Value)) + }) }) diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go index 2469a871..d40d81df 100644 --- a/e2e/storage_class_test.go +++ b/e2e/storage_class_test.go @@ -3,64 +3,148 @@ // Copyright 2020-2021 Clastix Labs // SPDX-License-Identifier: Apache-2.0 +// "sigs.k8s.io/controller-runtime/pkg/client" + package e2e import ( "context" + "fmt" + "strconv" + "strings" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" ) var _ = Describe("when Tenant handles Storage classes", func() { - tnt := &capsulev1beta2.Tenant{ + tntNoDefaults := &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "storage-class-selector", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ + { + Name: "selector", + Kind: "User", + }, + }, + StorageClasses: &api.DefaultAllowedListSpec{ + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + AllowedListSpec: api.AllowedListSpec{ + Exact: []string{"cephfs", "glusterfs"}, + Regex: "^oil-.*$", + }, + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "env": "customer", + }, + }, + }, + }, + }, + } + + tntWithDefault := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ - Name: "storage-class", + Name: "storage-class-default", }, Spec: capsulev1beta2.TenantSpec{ Owners: capsulev1beta2.OwnerListSpec{ { - Name: "storage", + Name: "default", Kind: "User", }, }, - StorageClasses: &api.SelectorAllowedListSpec{ - AllowedListSpec: api.AllowedListSpec{ - Exact: []string{ - "cephfs", - "glusterfs", + StorageClasses: &api.DefaultAllowedListSpec{ + Default: "tenant-default", + SelectorAllowedListSpec: api.SelectorAllowedListSpec{ + LabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": "tenant-default", + }, }, - Regex: "^oil-.*$", }, }, }, } + tenantDefault := storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant-default", + Labels: map[string]string{ + "name": "tenant-default", + "env": "e2e", + }, + }, + Provisioner: "kubernetes.io/no-provisioner", + } + globalDefault := storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global-default", + Labels: map[string]string{ + "env": "customer", + }, + }, + Provisioner: "kubernetes.io/no-provisioner", + } + disallowedGlobalDefault := storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "disallowed-global-default", + Labels: map[string]string{ + "name": "disallowed-global-default", + "env": "e2e", + }, + }, + Provisioner: "kubernetes.io/no-provisioner", + } + JustBeforeEach(func() { - EventuallyCreation(func() error { - tnt.ResourceVersion = "" - return k8sClient.Create(context.TODO(), tnt) - }).Should(Succeed()) + for _, tnt := range []*capsulev1beta2.Tenant{tntNoDefaults, tntWithDefault} { + EventuallyCreation(func() error { + tnt.ResourceVersion = "" + + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + } }) JustAfterEach(func() { - Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + for _, tnt := range []*capsulev1beta2.Tenant{tntNoDefaults, tntWithDefault} { + Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + } + + Eventually(func() (err error) { + req, _ := labels.NewRequirement("env", selection.Exists, nil) + + return k8sClient.DeleteAllOf(context.TODO(), &storagev1.StorageClass{}, &client.DeleteAllOfOptions{ + ListOptions: client.ListOptions{ + LabelSelector: labels.NewSelector().Add(*req), + }, + }) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) - It("should fails", func() { + It("should fail", func() { + k8sClient.Create(context.TODO(), tntNoDefaults) + ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("non-specifying it", func() { Eventually(func() (err error) { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0]) p := &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "denied-pvc", @@ -80,7 +164,7 @@ var _ = Describe("when Tenant handles Storage classes", func() { }) By("specifying a forbidden one", func() { Eventually(func() (err error) { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0]) p := &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "mighty-storage", @@ -98,16 +182,54 @@ var _ = Describe("when Tenant handles Storage classes", func() { return }, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed()) }) + By("specifying with not matching label", func() { + for i, sc := range []string{"internal-hdd", "internal-ssd"} { + storageName := strings.Join([]string{sc, "-", strconv.Itoa(i)}, "") + class := &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("sc-%s", storageName), + Labels: map[string]string{ + "env": "internal", + }, + }, + Provisioner: "kubernetes.io/no-provisioner", + } + Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) + + p := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: storageName, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + StorageClassName: &storageName, + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceStorage: resource.MustParse("3Gi"), + }, + }, + }, + } + + cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + + EventuallyCreation(func() error { + _, err := cs.CoreV1().PersistentVolumeClaims(ns.GetName()).Create(context.Background(), p, metav1.CreateOptions{}) + return err + }).ShouldNot(Succeed()) + } + }) + }) It("should allow", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0]) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("using exact matches", func() { - for _, c := range tnt.Spec.StorageClasses.Exact { + for _, c := range tntNoDefaults.Spec.StorageClasses.Exact { Eventually(func() (err error) { p := &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ @@ -149,5 +271,156 @@ var _ = Describe("when Tenant handles Storage classes", func() { return }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) + By("using a selector match", func() { + for i, sc := range []string{"customer-hdd", "customer-ssd"} { + storageName := strings.Join([]string{sc, "-", strconv.Itoa(i)}, "") + class := &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: storageName, + Labels: map[string]string{ + "env": "customer", + }, + }, + Provisioner: "kubernetes.io/no-provisioner", + } + Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) + + EventuallyCreation(func() error { + p := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: storageName, + Namespace: ns.GetName(), + }, + Spec: corev1.PersistentVolumeClaimSpec{ + StorageClassName: &storageName, + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceStorage: resource.MustParse("3Gi"), + }, + }, + }, + } + + return k8sClient.Create(context.Background(), p) + }).Should(Succeed()) + } + }) + }) + + It("should mutate to default tenant StorageClass (class does not exists)", func() { + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + p := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-default-sc", + Namespace: ns.GetName(), + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceStorage: resource.MustParse("3Gi"), + }, + }, + }, + } + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), p) + }).Should(Succeed()) + Expect(*p.Spec.StorageClassName).To(Equal("tenant-default")) + }) + + It("should mutate to default tenant StorageClass (class exists)", func() { + class := tenantDefault + Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed()) + + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + p := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-default-sc-present", + Namespace: ns.GetName(), + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceStorage: resource.MustParse("3Gi"), + }, + }, + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), p) + }).Should(Succeed()) + Expect(*p.Spec.StorageClassName).To(Equal(class.GetName())) + }) + + It("should mutate to default tenant StorageClass although cluster global ons is not allowed", func() { + class := tenantDefault + global := disallowedGlobalDefault + + Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed()) + Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed()) + + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + p := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-default-sc-present", + Namespace: ns.GetName(), + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceStorage: resource.MustParse("3Gi"), + }, + }, + }, + } + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), p) + }).Should(Succeed()) + Expect(*p.Spec.StorageClassName).To(Equal(class.GetName())) + }) + + It("should mutate to default tenant StorageClass although cluster global ons is allowed", func() { + class := tenantDefault + global := globalDefault + + Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed()) + Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed()) + + ns := NewNamespace("") + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + + p := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pvc-default-sc-present", + Namespace: ns.GetName(), + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceStorage: resource.MustParse("3Gi"), + }, + }, + }, + } + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), p) + }).Should(Succeed()) + Expect(*p.Spec.StorageClassName).To(Equal(class.GetName())) }) }) From ea71dd7e6564bf076ed3b1293827e884ad18bcaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 5 Jan 2023 17:27:05 +0100 Subject: [PATCH 066/153] fix(e2e): correct e2e test for container patches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- e2e/container_registry_test.go | 111 +++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 32 deletions(-) diff --git a/e2e/container_registry_test.go b/e2e/container_registry_test.go index 1573f1f8..4a1a3bfb 100644 --- a/e2e/container_registry_test.go +++ b/e2e/container_registry_test.go @@ -7,6 +7,7 @@ package e2e import ( "context" + "encoding/json" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -18,6 +19,12 @@ import ( "github.com/clastix/capsule/pkg/api" ) +type Patch struct { + Op string `json:"op"` + Path string `json:"path"` + Value string `json:"value"` +} + var _ = Describe("enforcing a Container Registry", func() { tnt := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ @@ -112,8 +119,7 @@ var _ = Describe("enforcing a Container Registry", func() { }) It("should deny patching a not matching registry after applying with a matching (Container)", func() { - ns := NewNamespace("reg-deny-container-patch") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + ns := NewNamespace("") pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -130,23 +136,33 @@ var _ = Describe("enforcing a Container Registry", func() { } cs := ownerClient(tnt.Spec.Owners[0]) + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) return err }).Should(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: pod.GetName(), Namespace: ns.GetName()}, pod)).ToNot(HaveOccurred()) - - pod.Spec.Containers[0].Image = "attacker/google-containers/pause-amd64:3.0" - Expect(k8sClient.Update(context.Background(), pod)).To(HaveOccurred()) - - Expect(k8sClient.Delete(context.TODO(), ns)).Should(Succeed()) + Eventually(func() error { + payload := []Patch{{ + Op: "replace", + Path: "/spec/containers/0/image", + Value: "attacker/google-containers/pause-amd64:3.0", + }} + payloadBytes, _ := json.Marshal(payload) + _, err := cs.CoreV1().Pods(ns.GetName()).Patch(context.TODO(), pod.GetName(), types.JSONPatchType, payloadBytes, metav1.PatchOptions{}) + if err != nil { + return err + } + return nil + }).ShouldNot(Succeed()) }) It("should deny patching a not matching registry after applying with a matching (initContainer)", func() { - ns := NewNamespace("reg-deny-init-patch") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + ns := NewNamespace("") pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -169,23 +185,33 @@ var _ = Describe("enforcing a Container Registry", func() { } cs := ownerClient(tnt.Spec.Owners[0]) + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) return err }).Should(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: pod.GetName(), Namespace: ns.GetName()}, pod)).ToNot(HaveOccurred()) - - pod.Spec.InitContainers[0].Image = "attacker/google-containers/pause-amd64:3.0" - Expect(k8sClient.Update(context.Background(), pod)).To(HaveOccurred()) - - Expect(k8sClient.Delete(context.TODO(), ns)).Should(Succeed()) + Eventually(func() error { + payload := []Patch{{ + Op: "replace", + Path: "/spec/initContainers/0/image", + Value: "attacker/google-containers/pause-amd64:3.0", + }} + payloadBytes, _ := json.Marshal(payload) + _, err := cs.CoreV1().Pods(ns.GetName()).Patch(context.TODO(), pod.GetName(), types.JSONPatchType, payloadBytes, metav1.PatchOptions{}) + if err != nil { + return err + } + return nil + }).ShouldNot(Succeed()) }) It("should allow patching a matching registry after applying with a matching (Container)", func() { - ns := NewNamespace("reg-allow-container-patch") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + ns := NewNamespace("") pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -202,23 +228,33 @@ var _ = Describe("enforcing a Container Registry", func() { } cs := ownerClient(tnt.Spec.Owners[0]) + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) return err }).Should(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: pod.GetName(), Namespace: ns.GetName()}, pod)).ToNot(HaveOccurred()) - - pod.Spec.Containers[0].Image = "myregistry.azurecr.io/google-containers/pause-amd64:3.1" - Expect(k8sClient.Update(context.Background(), pod)).To(HaveOccurred()) - - Expect(k8sClient.Delete(context.TODO(), ns)).Should(Succeed()) + Eventually(func() error { + payload := []Patch{{ + Op: "replace", + Path: "/spec/containers/0/image", + Value: "myregistry.azurecr.io/google-containers/pause-amd64:3.1", + }} + payloadBytes, _ := json.Marshal(payload) + _, err := cs.CoreV1().Pods(ns.GetName()).Patch(context.TODO(), pod.GetName(), types.JSONPatchType, payloadBytes, metav1.PatchOptions{}) + if err != nil { + return err + } + return nil + }).Should(Succeed()) }) It("should allow patching a matching registry after applying with a matching (initContainer)", func() { - ns := NewNamespace("reg-allow-container-patch") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + ns := NewNamespace("") pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -241,18 +277,29 @@ var _ = Describe("enforcing a Container Registry", func() { } cs := ownerClient(tnt.Spec.Owners[0]) + + NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) return err }).Should(Succeed()) - Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: pod.GetName(), Namespace: ns.GetName()}, pod)).ToNot(HaveOccurred()) - - pod.Spec.InitContainers[0].Image = "myregistry.azurecr.io/google-containers/pause-amd64:3.1" - Expect(k8sClient.Update(context.Background(), pod)).To(HaveOccurred()) - - Expect(k8sClient.Delete(context.TODO(), ns)).Should(Succeed()) + Eventually(func() error { + payload := []Patch{{ + Op: "replace", + Path: "/spec/initContainers/0/image", + Value: "myregistry.azurecr.io/google-containers/pause-amd64:3.1", + }} + payloadBytes, _ := json.Marshal(payload) + _, err := cs.CoreV1().Pods(ns.GetName()).Patch(context.TODO(), pod.GetName(), types.JSONPatchType, payloadBytes, metav1.PatchOptions{}) + if err != nil { + return err + } + return nil + }).Should(Succeed()) }) It("should allow using an exact match", func() { From 25095bcf2d00ce0f11dd241e1dca47efd8fc840a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 5 Jan 2023 17:27:17 +0100 Subject: [PATCH 067/153] chore(makefile): add defaults handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e444eb95..1d1df373 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,10 @@ dev-setup: export CA_BUNDLE=`openssl base64 -in /tmp/k8s-webhook-server/serving-certs/tls.crt | tr -d '\n'`; \ kubectl patch MutatingWebhookConfiguration capsule-mutating-webhook-configuration \ --type='json' -p="[\ - {'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/namespace-owner-reference\",'caBundle':\"$${CA_BUNDLE}\"}}\ + {'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/defaults\",'caBundle':\"$${CA_BUNDLE}\"}},\ + {'op': 'replace', 'path': '/webhooks/1/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/defaults\",'caBundle':\"$${CA_BUNDLE}\"}},\ + {'op': 'replace', 'path': '/webhooks/2/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/defaults\",'caBundle':\"$${CA_BUNDLE}\"}},\ + {'op': 'replace', 'path': '/webhooks/3/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/namespace-owner-reference\",'caBundle':\"$${CA_BUNDLE}\"}}\ ]" && \ kubectl patch ValidatingWebhookConfiguration capsule-validating-webhook-configuration \ --type='json' -p="[\ From b58cb19ea1c2c3e07f567bf177f9f909f38869de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Thu, 5 Jan 2023 17:27:32 +0100 Subject: [PATCH 068/153] chore(gitignore): add kind.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b9a90ae4..be2d6482 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ bin **/*.key .DS_Store *.tgz +kind.yaml From ea88b102e5ae04272efb17187d01af3606e29ae9 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Mon, 23 Jan 2023 14:26:44 +0100 Subject: [PATCH 069/153] feat: pv labelling and preventing cross-tenant mount --- controllers/pv/controller.go | 117 ++++++++++++++++++++++++++++++++++ main.go | 10 ++- pkg/webhook/pvc/errors.go | 48 ++++++++++++++ pkg/webhook/pvc/pv.go | 98 ++++++++++++++++++++++++++++ pkg/webhook/pvc/validating.go | 12 ++-- 5 files changed, 277 insertions(+), 8 deletions(-) create mode 100644 controllers/pv/controller.go create mode 100644 pkg/webhook/pvc/pv.go diff --git a/controllers/pv/controller.go b/controllers/pv/controller.go new file mode 100644 index 00000000..3ef3ebae --- /dev/null +++ b/controllers/pv/controller.go @@ -0,0 +1,117 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package pv + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/util/retry" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + log2 "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + capsuleutils "github.com/clastix/capsule/pkg/utils" + webhookutils "github.com/clastix/capsule/pkg/webhook/utils" +) + +type Controller struct { + client client.Client + label string +} + +func (c *Controller) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + log := log2.FromContext(ctx) + + persistentVolume := corev1.PersistentVolume{} + if err := c.client.Get(ctx, request.NamespacedName, &persistentVolume); err != nil { + if errors.IsNotFound(err) { + log.Info("skipping reconciliation, resource may have been deleted") + + return reconcile.Result{}, nil + } + + log.Error(err, "cannot retrieve corev1.PersistentVolume") + + return reconcile.Result{}, err + } + + if persistentVolume.Spec.ClaimRef == nil { + log.Info("skipping reconciliation, missing claimRef") + + return reconcile.Result{}, nil + } + + tnt, err := webhookutils.TenantByStatusNamespace(ctx, c.client, persistentVolume.Spec.ClaimRef.Namespace) + if err != nil { + log.Error(err, "unable to retrieve Tenant from the claimRef") + + return reconcile.Result{}, err + } + + if tnt == nil { + log.Info("skipping reconciliation, PV is claimed by a PVC not managed in a Tenant") + + return reconcile.Result{}, nil + } + + retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + pv := persistentVolume + + if err = c.client.Get(ctx, request.NamespacedName, &pv); err != nil { + return err + } + + labels := pv.GetLabels() + if labels == nil { + labels = map[string]string{} + } + + labels[c.label] = tnt.GetName() + + pv.SetLabels(labels) + + return c.client.Update(ctx, &pv) + }) + if retryErr != nil { + log.Error(retryErr, "unable to update PersistentVolume with Capsule label") + + return reconcile.Result{}, retryErr + } + + return reconcile.Result{}, nil +} + +func (c *Controller) SetupWithManager(mgr ctrl.Manager) error { + label, err := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{}) + if err != nil { + return err + } + + c.client = mgr.GetClient() + c.label = label + + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.PersistentVolume{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { + pv, ok := object.(*corev1.PersistentVolume) + if !ok { + return false + } + + if pv.Spec.ClaimRef == nil { + return false + } + + labels := object.GetLabels() + _, ok = labels[c.label] + + return !ok + }))). + Complete(c) +} diff --git a/main.go b/main.go index 323f66a1..a69e8de4 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,7 @@ import ( capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" configcontroller "github.com/clastix/capsule/controllers/config" + "github.com/clastix/capsule/controllers/pv" rbaccontroller "github.com/clastix/capsule/controllers/rbac" "github.com/clastix/capsule/controllers/resources" servicelabelscontroller "github.com/clastix/capsule/controllers/servicelabels" @@ -95,7 +96,7 @@ func newDelegatingClient(cache cache.Cache, config *rest.Config, options client. return delegatingClient, nil } -// nolint:maintidx +// nolint:maintidx,cyclop func main() { var enableLeaderElection, version bool @@ -239,7 +240,7 @@ func main() { route.Pod(pod.ImagePullPolicy(), pod.ContainerRegistry(), pod.PriorityClass(), pod.RuntimeClass()), route.Namespace(utils.InCapsuleGroups(cfg, namespacewebhook.PatchHandler(), namespacewebhook.QuotaHandler(), namespacewebhook.FreezeHandler(cfg), namespacewebhook.PrefixHandler(cfg), namespacewebhook.UserMetadataHandler())), route.Ingress(ingress.Class(cfg, kubeVersion), ingress.Hostnames(cfg), ingress.Collision(cfg), ingress.Wildcard()), - route.PVC(pvc.Handler()), + route.PVC(pvc.Validating(), pvc.PersistentVolumeReuse()), route.Service(service.Handler()), route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())), route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler(), tenant.ForbiddenAnnotationsRegexHandler(), tenant.ProtectedHandler()), @@ -296,6 +297,11 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "EndpointSliceLabels") } + if err = (&pv.Controller{}).SetupWithManager(manager); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "PersistentVolume") + os.Exit(1) + } + if err = (&configcontroller.Manager{ Log: ctrl.Log.WithName("controllers").WithName("CapsuleConfiguration"), }).SetupWithManager(manager, configurationName); err != nil { diff --git a/pkg/webhook/pvc/errors.go b/pkg/webhook/pvc/errors.go index f02789ae..11337172 100644 --- a/pkg/webhook/pvc/errors.go +++ b/pkg/webhook/pvc/errors.go @@ -43,3 +43,51 @@ func (f storageClassForbiddenError) Error() string { return utils.DefaultAllowedValuesErrorMessage(f.spec, msg) } + +type missingPVLabelsError struct { + name string +} + +func NewMissingPVLabelsError(name string) error { + return &missingPVLabelsError{name: name} +} + +func (m missingPVLabelsError) Error() string { + return fmt.Sprintf("PeristentVolume %s is missing any label, please, ask the Cluster Administrator to label it", m.name) +} + +type missingPVTenantLabelsError struct { + name string +} + +func NewMissingTenantPVLabelsError(name string) error { + return &missingPVTenantLabelsError{name: name} +} + +func (m missingPVTenantLabelsError) Error() string { + return fmt.Sprintf("PeristentVolume %s is missing the Capsule Tenant label, preventing a potential cross-tenant mount", m.name) +} + +type crossTenantPVMountError struct { + name string +} + +func NewCrossTenantPVMountError(name string) error { + return &crossTenantPVMountError{ + name: name, + } +} + +func (m crossTenantPVMountError) Error() string { + return fmt.Sprintf("PeristentVolume %s cannot be used by the following Tenant, preventing a cross-tenant mount", m.name) +} + +type pvSelectorError struct{} + +func NewPVSelectorError() error { + return &pvSelectorError{} +} + +func (m pvSelectorError) Error() string { + return "PersistentVolume selectors are not allowed since unable to prevent cross-tenant mount" +} diff --git a/pkg/webhook/pvc/pv.go b/pkg/webhook/pvc/pv.go new file mode 100644 index 00000000..06d65791 --- /dev/null +++ b/pkg/webhook/pvc/pv.go @@ -0,0 +1,98 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package pvc + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + capsulewebhook "github.com/clastix/capsule/pkg/webhook" + "github.com/clastix/capsule/pkg/webhook/utils" +) + +type PV struct { + capsuleLabel string +} + +func PersistentVolumeReuse() capsulewebhook.Handler { + value, err := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{}) + if err != nil { + panic(fmt.Sprintf("this shouldn't happen: %s", err.Error())) + } + + return &PV{ + capsuleLabel: value, + } +} + +func (p PV) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + pvc := corev1.PersistentVolumeClaim{} + if err := decoder.Decode(req, &pvc); err != nil { + return utils.ErroredResponse(err) + } + + tnt, err := utils.TenantByStatusNamespace(ctx, client, pvc.GetNamespace()) + if err != nil { + return utils.ErroredResponse(err) + } + // PVC is not in a Tenant Namespace, skipping + if tnt == nil { + return nil + } + // A PersistentVolume selector cannot help in preventing a cross-tenant mount: + // thus, disallowing that in first place. + if pvc.Spec.Selector != nil { + return utils.ErroredResponse(NewPVSelectorError()) + } + // The PVC hasn't any volumeName pre-claimed, it can be skipped + if len(pvc.Spec.VolumeName) == 0 { + return nil + } + // Checking if the PV is labelled with the Tenant name + pv := corev1.PersistentVolume{} + if err = client.Get(ctx, types.NamespacedName{Name: pvc.Spec.VolumeName}, &pv); err != nil { + if errors.IsNotFound(err) { + err = fmt.Errorf("cannot create a PVC referring to a not yet existing PV") + } + + return utils.ErroredResponse(err) + } + + if pv.GetLabels() == nil { + return utils.ErroredResponse(NewMissingPVLabelsError(pv.GetName())) + } + + value, ok := pv.GetLabels()[p.capsuleLabel] + if !ok { + return utils.ErroredResponse(NewMissingTenantPVLabelsError(pv.GetName())) + } + + if value != p.capsuleLabel { + return utils.ErroredResponse(NewCrossTenantPVMountError(pv.GetName())) + } + + return nil + } +} + +func (p PV) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +} + +func (p PV) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +} diff --git a/pkg/webhook/pvc/validating.go b/pkg/webhook/pvc/validating.go index 52d2dc70..03b6fb2c 100644 --- a/pkg/webhook/pvc/validating.go +++ b/pkg/webhook/pvc/validating.go @@ -17,13 +17,13 @@ import ( "github.com/clastix/capsule/pkg/webhook/utils" ) -type handler struct{} +type validating struct{} -func Handler() capsulewebhook.Handler { - return &handler{} +func Validating() capsulewebhook.Handler { + return &validating{} } -func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { +func (h *validating) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { pvc := &corev1.PersistentVolumeClaim{} if err := decoder.Decode(req, pvc); err != nil { @@ -87,13 +87,13 @@ func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder } } -func (h *handler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *validating) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { return nil } } -func (h *handler) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *validating) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { return nil } From 18a9e77a8a43e49982f8a959b5ea463aa263f8de Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Mon, 23 Jan 2023 15:17:07 +0100 Subject: [PATCH 070/153] test(e2e): preventing persistentvolume cross tenant mount --- e2e/preventing_pv_cross_tenant_mount_test.go | 199 +++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 e2e/preventing_pv_cross_tenant_mount_test.go diff --git a/e2e/preventing_pv_cross_tenant_mount_test.go b/e2e/preventing_pv_cross_tenant_mount_test.go new file mode 100644 index 00000000..c3834348 --- /dev/null +++ b/e2e/preventing_pv_cross_tenant_mount_test.go @@ -0,0 +1,199 @@ +//go:build e2e + +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +var _ = Describe("preventing PersistentVolume cross-tenant mount", func() { + tnt1 := &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pv-one", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ + { + Name: "jessica", + Kind: "User", + }, + }, + }, + } + + tnt2 := &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pv-two", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: capsulev1beta2.OwnerListSpec{ + { + Name: "leto", + Kind: "User", + }, + }, + }, + } + + JustBeforeEach(func() { + for _, tnt := range []*capsulev1beta2.Tenant{tnt1, tnt2} { + EventuallyCreation(func() error { + tnt.ResourceVersion = "" + + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + } + }) + + JustAfterEach(func() { + for _, tnt := range []*capsulev1beta2.Tenant{tnt1, tnt2} { + Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + } + }) + + It("should add labels to PersistentVolume and prevent cross-Tenant mount", func() { + ns := NewNamespace("") + NamespaceCreation(ns, tnt1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tnt1, defaultTimeoutInterval).Should(ContainElement(ns.Name)) + + pvc := corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "arrakis", + Namespace: ns.Name, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + StorageClassName: pointer.String("standard"), + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), &pvc) + }).Should(Succeed()) + + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "arrakis-pod", + Namespace: ns.Name, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "gcr.io/google_containers/pause-amd64:3.0", + ImagePullPolicy: corev1.PullAlways, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "data", + MountPath: "/tmp", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "data", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: pvc.Name, + }, + }, + }, + }, + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(context.Background(), &pod) + }).Should(Succeed()) + + Eventually(func() int { + nsName := types.NamespacedName{Name: pvc.Name, Namespace: pvc.Namespace} + + if err := k8sClient.Get(context.Background(), nsName, &pvc); err != nil { + return 0 + } + + return len(pvc.Spec.VolumeName) + }, defaultTimeoutInterval, defaultPollInterval).Should(BeNumerically(">", 0)) + + pv := corev1.PersistentVolume{} + defer func() { + _ = k8sClient.Delete(context.Background(), &pv) + }() + + Eventually(func() string { + if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: pvc.Spec.VolumeName}, &pv); err != nil { + return "not-found" + } + + if pv.GetLabels() == nil { + return "no-labels" + } + + return pv.GetLabels()["capsule.clastix.io/tenant"] + }, defaultTimeoutInterval, defaultPollInterval).Should(Equal(tnt1.Name)) + + Eventually(func() error { + nsName := types.NamespacedName{Name: pv.Name} + + if err := k8sClient.Get(context.Background(), nsName, &pv); err != nil { + return err + } + + pv.Spec.PersistentVolumeReclaimPolicy = corev1.PersistentVolumeReclaimRecycle + + return k8sClient.Update(context.Background(), &pv) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + + Expect(k8sClient.Delete(context.Background(), &pod, &client.DeleteOptions{GracePeriodSeconds: pointer.Int64(0)})).ToNot(HaveOccurred()) + + ns2 := NewNamespace("") + NamespaceCreation(ns2, tnt2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(tnt2, defaultTimeoutInterval).Should(ContainElement(ns2.Name)) + + Consistently(func() error { + pvc := corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "caladan", + Namespace: ns2.Name, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + StorageClassName: pointer.String("standard"), + VolumeName: pv.Name, + }, + } + + return k8sClient.Create(context.Background(), &pvc) + }, defaultTimeoutInterval, defaultPollInterval).Should(HaveOccurred()) + }) +}) From 1f0ac0724b760350b2f6f8799b4fc01fafa14a9f Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Mon, 23 Jan 2023 17:25:35 +0100 Subject: [PATCH 071/153] docs: preventing persistentvolume cross tenant mount --- docs/content/general/tutorial.md | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/docs/content/general/tutorial.md b/docs/content/general/tutorial.md index 7132252a..1b02675b 100644 --- a/docs/content/general/tutorial.md +++ b/docs/content/general/tutorial.md @@ -2058,6 +2058,77 @@ Eventually, using the key `namespacedItem`, it is possible to reference existing As with `GlobalTenantResource`, the full reference of the API is available in the [CRDs API section](/docs/general/crds-apis). +## Preventing PersistentVolume cross mounting across Tenants + +Any Tenant owner is able to create a `PersistentVolumeClaim` that, backed by a given _StorageClass_, will provide volumes for their applications. + +In most cases, once a `PersistentVolumeClaim` is deleted, the bounded `PersistentVolume` will be recycled due. + +However, in some scenarios, the `StorageClass` or the provisioned `PersistentVolume` itself could change the retention policy of the volume, keeping it available for recycling and being consumable for another Pod. + +In such a scenario, Capsule enforces the Volume mount only to the Namespaces belonging to the Tenant on which it's been consumed, by adding a label to the Volume as follows. + +```yaml +apiVersion: v1 +kind: PersistentVolume +metadata: + annotations: + pv.kubernetes.io/provisioned-by: rancher.io/local-path + creationTimestamp: "2022-12-22T09:54:46Z" + finalizers: + - kubernetes.io/pv-protection + labels: + capsule.clastix.io/tenant: atreides + name: pvc-1b3aa814-3b0c-4912-9bd9-112820da38fe + resourceVersion: "2743059" + uid: 9836ae3e-4adb-41d2-a416-0c45c2da41ff +spec: + accessModes: + - ReadWriteOnce + capacity: + storage: 10Gi + claimRef: + apiVersion: v1 + kind: PersistentVolumeClaim + name: melange + namespace: caladan + resourceVersion: "2743014" + uid: 1b3aa814-3b0c-4912-9bd9-112820da38fe +``` + +Once the `PeristentVolume` become available again, it can be referenced by any `PersistentVolumeClaim` in the `atreides` Tenant Namespace resources. + +If another Tenant, like `harkonnen`, tries to use it, it will get an error: + +``` +$: k describe pv pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d +Name: pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d +Labels: capsule.clastix.io/tenant=atreides +Annotations: pv.kubernetes.io/provisioned-by: rancher.io/local-path +Finalizers: [kubernetes.io/pv-protection] +StorageClass: standard +Status: Available +... + +$: cat /tmp/pvc.yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: melange + namespace: harkonnen +spec: + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 3Gi + volumeName: pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d + +$: k apply -f /tmp/pvc.yaml +Error from server: error when creating "/tmp/pvc.yaml": admission webhook "pvc.capsule.clastix.io" denied the request: PeristentVolume pvc-9788f5e4-1114-419b-a830-74e7f9a33f5d cannot be used by the following Tenant, preventing a cross-tenant mount +``` + --- This ends our tutorial on how to implement complex multi-tenancy and policy-driven scenarios with Capsule. As we improve it, more use cases about multi-tenancy, policy admission control, and cluster governance will be covered in the future. From 8e827b2f5b67565461b560481b2ee769113b47af Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 26 Jan 2023 09:23:43 +0100 Subject: [PATCH 072/153] fix(api): adding required omitempty for ux --- api/v1beta2/ingress_options.go | 2 +- api/v1beta2/namespace_options.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/v1beta2/ingress_options.go b/api/v1beta2/ingress_options.go index 3fe8c4cd..740b1503 100644 --- a/api/v1beta2/ingress_options.go +++ b/api/v1beta2/ingress_options.go @@ -29,5 +29,5 @@ type IngressOptions struct { // Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. AllowedHostnames *api.AllowedListSpec `json:"allowedHostnames,omitempty"` // Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. - AllowWildcardHostnames bool `json:"allowWildcardHostnames"` + AllowWildcardHostnames bool `json:"allowWildcardHostnames,omitempty"` } diff --git a/api/v1beta2/namespace_options.go b/api/v1beta2/namespace_options.go index 5be847c9..0b71d73f 100644 --- a/api/v1beta2/namespace_options.go +++ b/api/v1beta2/namespace_options.go @@ -14,7 +14,7 @@ type NamespaceOptions struct { // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"` // Define the labels that a Tenant Owner cannot set for their Namespace resources. - ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels"` + ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels,omitempty"` // Define the annotations that a Tenant Owner cannot set for their Namespace resources. - ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations"` + ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations,omitempty"` } From 1b51a4777d668a140c5c4eee511e24855aae212d Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 26 Jan 2023 09:24:02 +0100 Subject: [PATCH 073/153] chore(kustomize): adding required omitempty for ux --- config/crd/bases/capsule.clastix.io_tenants.yaml | 5 ----- config/install.yaml | 5 ----- 2 files changed, 10 deletions(-) diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index 8f3f4bd0..3ac0cb7b 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -2131,8 +2131,6 @@ spec: - Namespace - Disabled type: string - required: - - allowWildcardHostnames type: object limitRanges: description: Specifies the resource min/max usage restrictions to @@ -2267,9 +2265,6 @@ spec: format: int32 minimum: 1 type: integer - required: - - forbiddenAnnotations - - forbiddenLabels type: object networkPolicies: description: Specifies the NetworkPolicies assigned to the Tenant. diff --git a/config/install.yaml b/config/install.yaml index 00e83c9d..5bfdac11 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -1997,8 +1997,6 @@ spec: - Namespace - Disabled type: string - required: - - allowWildcardHostnames type: object limitRanges: description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. @@ -2109,9 +2107,6 @@ spec: format: int32 minimum: 1 type: integer - required: - - forbiddenAnnotations - - forbiddenLabels type: object networkPolicies: description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. From 91a979edc4d87813a6368890deb4d670d8f17a98 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 26 Jan 2023 09:24:09 +0100 Subject: [PATCH 074/153] chore(helm): adding required omitempty for ux --- charts/capsule/crds/tenant-crd.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index ae8b2a15..887e8956 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -1433,8 +1433,6 @@ spec: - Namespace - Disabled type: string - required: - - allowWildcardHostnames type: object limitRanges: description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. @@ -1545,9 +1543,6 @@ spec: format: int32 minimum: 1 type: integer - required: - - forbiddenAnnotations - - forbiddenLabels type: object networkPolicies: description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. From f8e212c2913a2e83725fec9bcfef2e10eae204cd Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 26 Jan 2023 09:24:36 +0100 Subject: [PATCH 075/153] docs: refactoring for v1beta2 --- docs/content/contributing/development.md | 4 +- docs/content/general/crds-apis.md | 46 ++-- docs/content/general/getting-started.md | 2 +- docs/content/general/mtb.md | 54 ++-- docs/content/general/proxy.md | 12 +- docs/content/general/tutorial.md | 326 +++++++++++------------ docs/content/guides/flux2-capsule.md | 6 +- docs/content/guides/pod-security.md | 6 +- 8 files changed, 229 insertions(+), 227 deletions(-) diff --git a/docs/content/contributing/development.md b/docs/content/contributing/development.md index 0f0c83b2..f002ae97 100644 --- a/docs/content/contributing/development.md +++ b/docs/content/contributing/development.md @@ -152,7 +152,7 @@ $ kubectl -n capsule-system logs --all-containers -l control-plane=controller-ma # You may have a try to deploy a Tenant too to make sure it works end to end $ kubectl apply -f - < Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard.
-
+ @@ -3518,24 +3518,24 @@ Specifies options for the Namespaces, such as additional metadata or maximum num - + - + - + - + - + @@ -3552,11 +3552,11 @@ Specifies options for the Namespaces, such as additional metadata or maximum num
false
defaultstring +
+
false
matchExpressions []objecttruefalse
allowedClasses object
forbiddenAnnotationsadditionalMetadata object - Define the annotations that a Tenant Owner cannot set for their Namespace resources.
+ Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
truefalse
forbiddenLabelsforbiddenAnnotations object - Define the labels that a Tenant Owner cannot set for their Namespace resources.
+ Define the annotations that a Tenant Owner cannot set for their Namespace resources.
truefalse
additionalMetadataforbiddenLabels object - Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
+ Define the labels that a Tenant Owner cannot set for their Namespace resources.
false
-### Tenant.spec.namespaceOptions.forbiddenAnnotations +### Tenant.spec.namespaceOptions.additionalMetadata -Define the annotations that a Tenant Owner cannot set for their Namespace resources. +Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. @@ -3568,15 +3568,15 @@ Define the annotations that a Tenant Owner cannot set for their Namespace resour - - + + - - + + @@ -3585,11 +3585,11 @@ Define the annotations that a Tenant Owner cannot set for their Namespace resour
denied[]stringannotationsmap[string]string
false
deniedRegexstringlabelsmap[string]string
-### Tenant.spec.namespaceOptions.forbiddenLabels +### Tenant.spec.namespaceOptions.forbiddenAnnotations -Define the labels that a Tenant Owner cannot set for their Namespace resources. +Define the annotations that a Tenant Owner cannot set for their Namespace resources. @@ -3618,11 +3618,11 @@ Define the labels that a Tenant Owner cannot set for their Namespace resources.
-### Tenant.spec.namespaceOptions.additionalMetadata +### Tenant.spec.namespaceOptions.forbiddenLabels -Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. +Define the labels that a Tenant Owner cannot set for their Namespace resources. @@ -3634,15 +3634,15 @@ Specifies additional labels and annotations the Capsule operator places on any N - - + + - - + + diff --git a/docs/content/general/getting-started.md b/docs/content/general/getting-started.md index cddfa1b3..5eed9121 100644 --- a/docs/content/general/getting-started.md +++ b/docs/content/general/getting-started.md @@ -35,7 +35,7 @@ Create the tenant as cluster admin: ```yaml kubectl create -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil diff --git a/docs/content/general/mtb.md b/docs/content/general/mtb.md index 390e839e..2d491b57 100644 --- a/docs/content/general/mtb.md +++ b/docs/content/general/mtb.md @@ -51,7 +51,7 @@ As cluster admin, create a tenant ```yaml kubectl create -f - < Please, note that, despite created with more restricted permissions, a tenant owner can still create namespaces in the tenant because he belongs to the `capsule.clastix.io` group. If you want a user not acting as tenant owner, but still operating in the tenant, you can assign additional `RoleBindings` without assigning him the tenant ownership. +> Please, note that, despite created with more restricted permissions, a tenant owner can still create namespaces in the tenant because he belongs to the `capsule.clastix.io` group. +> If you want a user not acting as tenant owner, but still operating in the tenant, you can assign additional `RoleBindings` without assigning him the tenant ownership. Custom ClusterRoles are also supported. Assuming the cluster admin creates: @@ -295,18 +296,19 @@ These permissions can be granted to Joe ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil - annotations: - clusterrolenames.capsule.clastix.io/user.joe: view,prometheus-servicemonitors-viewer spec: owners: - name: alice kind: User - name: joe kind: User + clusterRoles: + - view + - prometheus-servicemonitors-viewer EOF ``` @@ -315,51 +317,12 @@ For the given configuration, the resulting RoleBinding resources are the followi ``` kubectl -n oil-production get rolebindings NAME ROLE AGE -capsule-oil-0-admin ClusterRole/admin 8d -capsule-oil-1-capsule-namespace-deleter ClusterRole/capsule-namespace-deleter 8d -capsule-oil-2-view ClusterRole/view 11m -capsule-oil-3-prometheus-servicemonitors-viewer ClusterRole/prometheus-servicemonitors-viewer 18s +capsule-oil-0-admin ClusterRole/admin 90s +capsule-oil-1-capsule-namespace-deleter ClusterRole/capsule-namespace-deleter 90s +capsule-oil-2-view ClusterRole/view 90s +capsule-oil-3-prometheus-servicemonitors-viewer ClusterRole/prometheus-servicemonitors-viewer 25s ``` -> The pattern for the annotation is `clusterrolenames.capsule.clastix.io/${KIND}.${NAME}`. -> The placeholders `${KIND}` and `${NAME}` are referring to the Tenant Owner specification fields, both lower-cased. -> -> In the case of users that are identified using their email address, the symbol `@` wouldn't be supported by the RFC 1123. -> For such cases, the `@` symbol can be replaced with the placeholder `__AT__`. -> -> ```yaml -> apiVersion: capsule.clastix.io/v1beta1 -> kind: Tenant -> metadata: -> annotations: -> clusterrolenames.capsule.clastix.io/alice__AT__clastix.io: editor,manager -> spec: -> owners: -> - kind: User -> name: alice@org.tld -> - kind: User -> name: alice@clastix.io -> ``` -> -> Instead, with the resulting annotation key exceeding 63 characters length, the zero-based index of the owner can be specified as follows: -> -> ```yaml -> apiVersion: capsule.clastix.io/v1beta1 -> kind: Tenant -> metadata: -> annotations: -> clusterrolenames.capsule.clastix.io/1: editor,manager -> spec: -> owners: -> - kind: User -> name: alice@org.tld -> - kind: User -> name: very-long-user-name-that-breaks-rfc-1123@org.tld -> ``` -> -> This latter example will assign the roles `editor` and `manager`, assigned to the user `very-long-user-name-that-breaks-rfc-1123@org.tld`. - - ### Assign additional Role Bindings The tenant owner acts as admin of tenant namespaces. Other users can operate inside the tenant namespaces with different levels of permissions and authorizations. @@ -382,7 +345,7 @@ These permissions can be granted to a user without giving the role of tenant own ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -421,7 +384,7 @@ The cluster admin, can control how many namespaces Alice, creates by setting a q ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -454,7 +417,8 @@ status: oil-development oil-production oil-test - size: 3 # current namespace count + Size: 3 # current namespace count + State: Active ... ``` @@ -476,7 +440,7 @@ Bill, the cluster admin, creates multiple tenants having `alice` as owner: ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -491,7 +455,7 @@ and ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: gas @@ -506,7 +470,7 @@ Alternatively, the ownership can be assigned to a group called `oil-and-gas`: ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -521,7 +485,7 @@ and ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: gas @@ -564,7 +528,7 @@ Set resources quota for each namespace in the Alice's tenant by defining them in ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -716,7 +680,7 @@ By setting enforcement at the namespace level, i.e. `spec.resourceQuotas.scope=N Bill, the cluster admin, can also set Limit Ranges for each namespace in Alice's tenant by defining limits for pods and containers in the tenant spec: ```yaml -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -757,40 +721,54 @@ spec: Limits will be inherited by all the namespaces created by Alice. In our case, when Alice creates the namespace `oil-production`, Capsule creates the following: ```yaml +apiVersion: v1 +kind: LimitRange +metadata: + name: capsule-oil-0 + namespace: oil-production +spec: + limits: + - max: + cpu: "1" + memory: 1Gi + min: + cpu: 50m + memory: 5Mi + type: Pod +--- +apiVersion: v1 kind: LimitRange +metadata: + name: capsule-oil-1 + namespace: oil-production +spec: + limits: + - default: + cpu: 200m + memory: 100Mi + defaultRequest: + cpu: 100m + memory: 10Mi + max: + cpu: "1" + memory: 1Gi + min: + cpu: 50m + memory: 5Mi + type: Container +--- apiVersion: v1 +kind: LimitRange metadata: - name: limits + name: capsule-oil-2 namespace: oil-production - labels: - tenant: oil spec: limits: - - type: Pod - min: - cpu: "50m" - memory: "5Mi" - max: - cpu: "1" - memory: "1Gi" - - type: Container - defaultRequest: - cpu: "100m" - memory: "10Mi" - default: - cpu: "200m" - memory: "100Mi" - min: - cpu: "50m" - memory: "5Mi" - max: - cpu: "1" - memory: "1Gi" - - type: PersistentVolumeClaim - min: - storage: "1Gi" - max: - storage: "10Gi" + - max: + storage: 10Gi + min: + storage: 1Gi + type: PersistentVolumeClaim ``` > Note: being the limit range specific of single resources, there is no aggregate to count. @@ -831,9 +809,8 @@ spec: allowed: - custom allowedRegex: "^tier-.*$" - selector: - matchLabels: - env: "production" + matchLabels: + env: "production" EOF ``` @@ -845,7 +822,6 @@ With the said Tenant specification, Alice can create a Pod resource if `spec.pri If a Pod is going to use a non-allowed _Priority Class_, it will be rejected by the Validation Webhook enforcing it. - ### Assign Pod Priority Class as tenant default It's possible to assign each tenant a PriorityClass which will be used, if no PriorityClass is set on pod basis: @@ -865,9 +841,8 @@ spec: - custom default: "tenant-default" allowedRegex: "^tier-.*$" - selector: - matchLabels: - env: "production" + matchLabels: + env: "production" EOF ``` @@ -913,9 +888,8 @@ spec: allowed: - legacy allowedRegex: "^hardened-.*$" - selector: - matchLabels: - env: "production" + matchLabels: + env: "production" EOF ``` @@ -946,7 +920,7 @@ The label `pool=oil` is defined as node selector in the tenant manifest: ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -965,7 +939,7 @@ The Capsule controller makes sure that any namespace created in the tenant has t Multiple node selector labels can be defined as in the following snippet: ```yaml -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1009,9 +983,8 @@ spec: allowed: - legacy allowedRegex: ^\w+-lb$ - selector: - matchLabels: - env: "production" + matchLabels: + env: "production" EOF ``` @@ -1040,9 +1013,12 @@ spec: http: paths: - backend: - serviceName: nginx - servicePort: 80 + service: + name: nginx + port: + number: 80 path: / + pathType: ImplementationSpecific EOF ``` @@ -1068,9 +1044,8 @@ spec: - legacy default: "tenant-default" allowedRegex: ^\w+-lb$ - selector: - matchLabels: - env: "production" + matchLabels: + env: "production" EOF ``` @@ -1105,7 +1080,7 @@ Bill can control ingress hostnames in the `oil` tenant to force the applications ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1161,7 +1136,7 @@ In a multi-tenant environment, as more and more ingresses are defined, there is ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1248,9 +1223,8 @@ spec: - ceph-rbd - ceph-nfs allowedRegex: "^ceph-.*$" - selector: - matchLabels: - env: "production" + matchLabels: + env: "production" EOF ``` @@ -1301,9 +1275,8 @@ spec: - ceph-rbd - ceph-nfs allowedRegex: "^ceph-.*$" - selector: - matchLabels: - env: "production" + matchLabels: + env: "production" EOF ``` @@ -1341,7 +1314,7 @@ Bill can set network policies in the tenant manifest, according to the requireme ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1442,7 +1415,7 @@ To avoid this kind of attack, Bill, the cluster admin, can force Alice, the tena ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1468,7 +1441,7 @@ The spec `containerRegistries` addresses this task and can provide a combination ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1524,7 +1497,7 @@ Bill can assign this role to any namespace in the Alice's tenant by setting it i ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1579,7 +1552,7 @@ Starting from Capsule **v0.1.1**, this can be done using a special annotation in Imagine the case where a Custom Resource named `MySQL` in the API group `databases.acme.corp/v1` usage must be limited in the Tenant `oil`: this can be done as follows. ```yaml -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1606,7 +1579,7 @@ spec: When `alice` will create a `MySQL` instance in one of their Tenant Namespace, the Cluster Administrator can easily retrieve the overall usage. ```yaml -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1628,7 +1601,7 @@ Assigns additional labels and annotations to all namespaces created in the `oil` ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1660,7 +1633,7 @@ metadata: capsule.clastix.io/backup: "true" name: oil-production ownerReferences: - - apiVersion: capsule.clastix.io/v1beta1 + - apiVersion: capsule.clastix.io/v1beta2 blockOwnerDeletion: true controller: true kind: Tenant @@ -1678,7 +1651,7 @@ Assigns additional labels and annotations to all services created in the `oil` t ```yaml kubectl apply -f - << EOF -apiVersion: capsule.clastix.io/v1beta1 +apiVersion: capsule.clastix.io/v1beta2 kind: Tenant metadata: name: oil @@ -1724,20 +1697,36 @@ Bill needs to cordon a Tenant and its Namespaces for several reasons: With this said, the Tenant Owner and the related Service Account living into managed Namespaces, cannot proceed to any update, create or delete action. -This is possible just labeling the Tenant as follows: +This is possible by just toggling the specific Tenant specification: ```shell -kubectl label tenant oil capsule.clastix.io/cordon=enabled -tenant oil labeled +apiVersion: capsule.clastix.io/v1beta2 +kind: Tenant +metadata: + name: oil +spec: + cordoned: true + owners: + - kind: User + name: alice ``` Any operation performed by Alice, the Tenant Owner, will be rejected by the Admission controller. -Uncordoning can be done by removing the said label: +Uncordoning can be done by removing the said specification key: ```shell -$ kubectl label tenant oil capsule.clastix.io/cordon- -tenant.capsule.clastix.io/oil labeled +$ cat < Date: Fri, 27 Jan 2023 11:48:08 +0100 Subject: [PATCH 076/153] docs: migrating guidelines for v1beta2 api version --- docs/content/guides/upgrading.md | 155 +++++++++++++++++++++++++++---- docs/gridsome.server.js | 2 +- 2 files changed, 138 insertions(+), 19 deletions(-) diff --git a/docs/content/guides/upgrading.md b/docs/content/guides/upgrading.md index 4cc6a0e7..4a66dab9 100644 --- a/docs/content/guides/upgrading.md +++ b/docs/content/guides/upgrading.md @@ -1,17 +1,133 @@ -# Upgrading Tenant resource from v1alpha1 to v1beta1 version +# Capsule upgrading guide -With [Capsule v0.1.0](https://github.com/clastix/capsule/releases/tag/v0.1.0), the Tenant custom resource has been bumped to `v1beta1` from `v1alpha1` with additional fields addressing the new features implemented so far. +List of Tenant API changes: + +- [Capsule v0.1.0](https://github.com/clastix/capsule/releases/tag/v0.1.0) bump to `v1beta1` from `v1alpha1`. +- [Capsule v0.2.0](https://github.com/clastix/capsule/releases/tag/v0.1.0) bump to `v1beta2` from `v1beta1`, deprecating `v1alpha1`. This document aims to provide support and a guide on how to perform a clean upgrade to the latest API version in order to avoid service disruption and data loss. -## Backup your cluster +As an installation method, Helm is given for granted, YMMV using the `kustomize` manifest. + +## Considerations We strongly suggest performing a full backup of your Kubernetes cluster, such as storage and etcd. -Use your favorite tool according to your needs. +Use your favourite tool according to your needs. + +# Upgrading from v0.1.3 to v0.2.0 + +## Scale down the Capsule controller + +Using the `kubectl` or Helm, scale down the Capsule controller manager: this is required to avoid the old Capsule version from processing objects that aren't yet installed as a CRD. + +``` +helm upgrade -n capsule-system capsule --set "replicaCount=0" +``` + +> Ensure that all the Pods have been removed correctly. + +## Migrate manually the `CapsuleConfiguration` to the latest API version + +With the v0.2.0 release of Capsule and the new features introduced, the resource `CapsuleConfiguration` is offering a new API version, bumped to `v1beta1` from `v1alpha1`. + +Essentially, the `CapsuleConfiguration` is storing configuration flags that allow Capsule to be configured on the fly without requiring the operator to reload. +This resource is read at the operator init-time when the conversion webhook offered by Capsule is not yet ready to serve any request. + +Migrating from v0.1.3 to v0.2.0 requires a manual conversion of your `CapsuleConfiguration` according to the latest version (currently, `v1beta2`). +You can find further information about it at the section `CRDs APIs`. + +The deletion of the `CapsuleConfiguration` resource is required, along with the update of the related CRD. + +``` +kubectl delete capsuleconfiguration default +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.0/config/crd/bases/capsuleconfiguration-crd.yaml +``` + +During the Helm upgrade, a new `CapsuleConfiguration` will be created: please, refer to the Helm Chart values to pick up your desired settings. + +## Patch the Tenant custom resource definition + +Unfortunately, Helm doesn't manage the lifecycle of Custom Resource Definitions, additional details can be found [here](https://github.com/helm/community/blob/f9e06c16d89ccea1bea77c01a6a96ae3b309f823/architecture/crds.md). + +This process must be executed manually as follows: + +``` +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.0/config/crd/bases/globaltenantresources-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.0/config/crd/bases/tenant-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.0/config/crd/bases/tenantresources-crd.yaml +``` + +> We're giving for granted that Capsule is installed in the `capsule-system` Namespace. +> According to your needs you can change the Namespace at your wish, e.g.: +> +> ```bash +> CUSTOM_NS="tenancy-operations" +> +> for CR in capsuleconfigurations.capsule.clastix.io globaltenantresources.capsule.clastix.io tenantresources.capsule.clastix.io tenants.capsule.clastix.io; do +> kubectl patch crd capsuleconfigurations.capsule.clastix.io --type='json' -p=" [{'op': 'replace', 'path': '/spec/conversion/webhook/clientConfig/service/namespace', 'value': "${CUSTOM_NS}"}]" +> done +> ``` + +## Update your Capsule Helm chart + +Ensure to update the Capsule repository to fetch the latest changes. + +``` +helm repo update +``` + +The latest Chart must be used, at the current time, 0.3.0 is expected for Capsule v0.2.0, you can fetch the full list of available charts with the following command. + +``` +helm search repo -l clastix/capsule +``` + +Since the Tenant custom resource definition has been patched with new fields, we can install back Capsule using the provided Helm chart. + +``` +helm upgrade --install capsule clastix/capsule -n capsule-system --create-namespace --version 0.3.0 +``` + +This will start the Operator with the latest changes, and perform the required sync operations like: + +1. Ensuring the CA is still valid +2. Ensuring a TLS certificate is valid for the local webhook server +3. If not using the cert-manager integration, patching the Validating and Mutating Webhook Configuration resources with the Capsule CA +4. If not using the cert-manager integration, patching the Capsule's Custom Resource Definitions conversion webhook fields with the Capsule CA + +## Ensure the conversion webhook is working + +Kubernetes Custom Resource definitions provide a conversion webhook that is used by an Operator to perform a seamless conversion between resources with different versioning. + +With the fresh new installation, Capsule patches all the required moving parts to ensure this conversion is put in place and uses the latest version (actually, `v1beta2`) for presenting the Tenant resources. + +You can check this behaviour by issuing the following command: + +``` +$: kubectl get tenants.v1beta2.capsule.clastix.io +NAME NAMESPACE QUOTA NAMESPACE COUNT OWNER NAME OWNER KIND NODE SELECTOR AGE +oil 3 0 alice User {"kubernetes.io/os":"linux"} 3m43s +``` + +You should see all the previous Tenant resources converted in the new format and structure. + +``` +$: kubectl get tenants.v1beta2.capsule.clastix.io +NAME STATE NAMESPACE QUOTA NAMESPACE COUNT NODE SELECTOR AGE +oil Active 3 0 {"kubernetes.io/os":"linux"} 3m38s +``` + +> Resources are still persisted in etcd using the previous Tenant version (`v1beta1`) and the conversion is executed on-the-fly thanks to the conversion webhook. +> If you'd like to decrease the pressure on Capsule due to the conversion webhook, we suggest performing a resource patching using the command `kubectl replace`: +> in this way, the API Server will update the etcd key with the specification according to the new versioning, allowing to skip the conversion. +> +> The `kubectl replace` command must be triggered when the Capsule webhook is up and running to allow the conversion between versions. + +# Upgrading from < v0.1.0 up to v0.1.3 ## Uninstall the old Capsule release -If you're using Helm as package manager, all the Operator resources such as Deployment, Service, Role Binding, and etc. must be deleted. +If you're using Helm as package manager, all the Operator resources such as Deployment, Service, Role Binding, etc. must be deleted. ``` helm uninstall -n capsule-system capsule @@ -21,7 +137,7 @@ Ensure that everything has been removed correctly, especially the Secret resourc ## Patch the Tenant custom resource definition -Helm doesn't manage the lifecycle of Custom Resource Definitions, additional details can be found [here](https://github.com/helm/community/blob/f9e06c16d89ccea1bea77c01a6a96ae3b309f823/architecture/crds.md). +Unfortunately, Helm doesn't manage the lifecycle of Custom Resource Definitions, additional details can be found [here](https://github.com/helm/community/blob/f9e06c16d89ccea1bea77c01a6a96ae3b309f823/architecture/crds.md). This process must be executed manually as follows: @@ -36,23 +152,27 @@ kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.1.0/config Since the Tenant custom resource definition has been patched with new fields, we can install back Capsule using the provided Helm chart. ``` -helm upgrade --install capsule clastix/capsule -n capsule-system --create-namespace +helm upgrade --install capsule clastix/capsule -n capsule-system --create-namespace --version=DESIRED_VERSION ``` -This will start the Operator that will perform several required actions, such as: +> Please, note the `DESIRED_VERSION`: you have to pick the Helm chart version according to the Capsule version you'd like to upgrade to. +> +> You can retrieve it by browsing the GitHub source code picking the Capsule tag as ref and inspecting the file `Chart.yaml` available in the folder `charts/capsule`. -1. Generating a new CA -2. Generating new TLS certificates for the local webhook server -3. Patching the Validating and Mutating Webhook Configuration resources with the fresh new CA +This will start the operator that will perform several required actions, such as: + +1. Generating a new CA +2. Generating new TLS certificates for the local webhook server +3. Patching the Validating and Mutating Webhook Configuration resources with the fresh new CA 4. Patching the Custom Resource Definition tenant conversion webhook CA ## Ensure the conversion webhook is working -Kubernetes Custom Resource definitions provide a conversion webhook that is used by an Operator to perform seamless conversion between resources with different versioning. +Kubernetes Custom Resource definitions provide a conversion webhook that is used by an Operator to perform a seamless conversion between resources with different versioning. -With the fresh new installation, Capsule patched all the required moving parts to ensure this conversion is put in place, and using the latest version (actually, `v1beta1`) for presenting the Tenant resources. +With the fresh new installation, Capsule patched all the required moving parts to ensure this conversion is put in place and using the latest version (actually, `v1beta1`) for presenting the Tenant resources. -You can check this behavior by issuing the following command: +You can check this behaviour by issuing the following command: ``` $: kubectl get tenants.v1beta1.capsule.clastix.io @@ -60,7 +180,7 @@ NAME NAMESPACE QUOTA NAMESPACE COUNT OWNER NAME OWNER KIND NODE SELECT oil 3 0 alice User {"kubernetes.io/os":"linux"} 3m43s ``` -You should see all the previous Tenant resources converted in the new format and structure. +You should see all the previous Tenant resources converted into the new format and structure. ``` $: kubectl get tenants.v1beta1.capsule.clastix.io @@ -68,6 +188,5 @@ NAME STATE NAMESPACE QUOTA NAMESPACE COUNT NODE SELECTOR oil Active 3 0 {"kubernetes.io/os":"linux"} 3m38s ``` -> Resources are still persisted in etcd using the `v1alpha1` specification and the conversion is executed on-the-fly thanks to the conversion webhook. -> If you'd like to decrease the pressure on Capsule due to the conversion webhook, we suggest performing a resource patching using the command `kubectl replace`: -> in this way, the API Server will update the etcd key with the specification according to the new versioning, allowing to skip the conversion. +> Resources are still persisted in etcd using the v1alpha1 specification and the conversion is executed on-the-fly thanks to the conversion webhook. +> If you'd like to decrease the pressure on Capsule due to the conversion webhook, we suggest performing a resource patching using the command kubectl replace: in this way, the API Server will update the etcd key with the specification according to the new versioning, allowing to skip the conversion. diff --git a/docs/gridsome.server.js b/docs/gridsome.server.js index a4d02b80..1e168f93 100644 --- a/docs/gridsome.server.js +++ b/docs/gridsome.server.js @@ -71,7 +71,7 @@ module.exports = function (api) { path: '/docs/guides/velero' }, { - label: 'Upgrading Tenant version', + label: 'Upgrading Capsule', path: '/docs/guides/upgrading' }, { From 36f028135509bbfbeae25de231c12c8d037934b9 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 28 Jan 2023 11:43:50 +0100 Subject: [PATCH 077/153] chore(kustomize): bumping up to Capsule v0.2.0 --- config/install.yaml | 2 +- config/manager/kustomization.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/install.yaml b/config/install.yaml index 5bfdac11..ab461c92 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -2767,7 +2767,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: clastix/capsule:v0.1.3 + image: clastix/capsule:v0.2.0 imagePullPolicy: IfNotPresent name: manager ports: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 55f23e74..e195ab67 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -7,4 +7,4 @@ kind: Kustomization images: - name: controller newName: clastix/capsule - newTag: v0.1.3 + newTag: v0.2.0 From a334e682567e471fcd85b1bc8d2de5b92b4c311f Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 28 Jan 2023 11:43:09 +0100 Subject: [PATCH 078/153] chore(helm): release v0.3.0 --- charts/capsule/Chart.yaml | 4 ++-- charts/capsule/README.md | 2 +- charts/capsule/values.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 3516c786..63929b82 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,8 +21,8 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.2.0 +version: 0.3.0 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. -appVersion: 0.1.3 +appVersion: 0.2.0 diff --git a/charts/capsule/README.md b/charts/capsule/README.md index 3d34b8d3..fe55ee9f 100644 --- a/charts/capsule/README.md +++ b/charts/capsule/README.md @@ -67,7 +67,7 @@ Here the values you can override: | customAnnotations | object | `{}` | Additional annotations which will be added to all resources created by Capsule helm chart | | customLabels | object | `{}` | Additional labels which will be added to all resources created by Capsule helm chart | | jobs.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy of the helm chart job | -| jobs.image.repository | string | `"quay.io/clastix/kubectl"` | Set the image repository of the helm chart job | +| jobs.image.repository | string | `"clastix/kubectl"` | Set the image repository of the helm chart job | | jobs.image.tag | string | `""` | Set the image tag of the helm chart job | | mutatingWebhooksTimeoutSeconds | int | `30` | Timeout in seconds for mutating webhooks | | nodeSelector | object | `{}` | Set the node selector for the Capsule pod | diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index b3e89642..000cee59 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -101,7 +101,7 @@ podSecurityPolicy: jobs: image: # -- Set the image repository of the helm chart job - repository: quay.io/clastix/kubectl + repository: clastix/kubectl # -- Set the image pull policy of the helm chart job pullPolicy: IfNotPresent # -- Set the image tag of the helm chart job From de587919f8c679c64710ef545e7c800e4d4497a9 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 31 Jan 2023 16:02:53 +0100 Subject: [PATCH 079/153] fix(tenantresources): using actual resourceversion during createorupdate --- controllers/resources/processor.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go index 841fee19..6e381c7c 100644 --- a/controllers/resources/processor.go +++ b/controllers/resources/processor.go @@ -252,12 +252,13 @@ func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstru _, err = controllerutil.CreateOrUpdate(ctx, r.client, actual, func() error { UID := actual.GetUID() + rv := actual.GetResourceVersion() actual.SetUnstructuredContent(desired.Object) actual.SetNamespace(ns) actual.SetLabels(labels) actual.SetAnnotations(annotations) - actual.SetResourceVersion("") + actual.SetResourceVersion(rv) actual.SetUID(UID) return nil From 7ec7f3c69c6f520da6dbf31311fc11ebb42af55d Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 31 Jan 2023 19:32:05 +0100 Subject: [PATCH 080/153] fix: converting the status between v1beta1 and v1beta2 --- api/v1beta2/tenant_conversion_hub.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/api/v1beta2/tenant_conversion_hub.go b/api/v1beta2/tenant_conversion_hub.go index 45c03bb4..aa144bb5 100644 --- a/api/v1beta2/tenant_conversion_hub.go +++ b/api/v1beta2/tenant_conversion_hub.go @@ -154,7 +154,15 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { in.Status.Namespaces = src.Status.Namespaces in.Status.Size = src.Status.Size - in.Status.State = tenantState(src.Status.State) + + switch src.Status.State { + case capsulev1beta1.TenantStateActive: + in.Status.State = TenantStateActive + case capsulev1beta1.TenantStateCordoned: + in.Status.State = TenantStateCordoned + default: + in.Status.State = TenantStateActive + } return nil } @@ -265,5 +273,17 @@ func (in *Tenant) ConvertTo(raw conversion.Hub) error { dst.SetAnnotations(annotations) + dst.Status.Size = in.Status.Size + dst.Status.Namespaces = in.Status.Namespaces + + switch in.Status.State { + case TenantStateActive: + dst.Status.State = capsulev1beta1.TenantStateActive + case TenantStateCordoned: + dst.Status.State = capsulev1beta1.TenantStateCordoned + default: + dst.Status.State = capsulev1beta1.TenantStateActive + } + return nil } From 0fbf43ba0f14616c2e8dc4bafb68a1ca1e42afd6 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 31 Jan 2023 17:00:08 +0100 Subject: [PATCH 081/153] docs: bumping up to v0.2.1 --- docs/content/guides/upgrading.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/content/guides/upgrading.md b/docs/content/guides/upgrading.md index 4a66dab9..6815009b 100644 --- a/docs/content/guides/upgrading.md +++ b/docs/content/guides/upgrading.md @@ -14,7 +14,7 @@ As an installation method, Helm is given for granted, YMMV using the `kustomize` We strongly suggest performing a full backup of your Kubernetes cluster, such as storage and etcd. Use your favourite tool according to your needs. -# Upgrading from v0.1.3 to v0.2.0 +# Upgrading from v0.1.3 to v0.2.x ## Scale down the Capsule controller @@ -28,19 +28,19 @@ helm upgrade -n capsule-system capsule --set "replicaCount=0" ## Migrate manually the `CapsuleConfiguration` to the latest API version -With the v0.2.0 release of Capsule and the new features introduced, the resource `CapsuleConfiguration` is offering a new API version, bumped to `v1beta1` from `v1alpha1`. +With the v0.2.x release of Capsule and the new features introduced, the resource `CapsuleConfiguration` is offering a new API version, bumped to `v1beta1` from `v1alpha1`. Essentially, the `CapsuleConfiguration` is storing configuration flags that allow Capsule to be configured on the fly without requiring the operator to reload. This resource is read at the operator init-time when the conversion webhook offered by Capsule is not yet ready to serve any request. -Migrating from v0.1.3 to v0.2.0 requires a manual conversion of your `CapsuleConfiguration` according to the latest version (currently, `v1beta2`). +Migrating from v0.1.3 to v0.2.x requires a manual conversion of your `CapsuleConfiguration` according to the latest version (currently, `v1beta2`). You can find further information about it at the section `CRDs APIs`. The deletion of the `CapsuleConfiguration` resource is required, along with the update of the related CRD. ``` kubectl delete capsuleconfiguration default -kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.0/config/crd/bases/capsuleconfiguration-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/config/crd/bases/capsuleconfiguration-crd.yaml ``` During the Helm upgrade, a new `CapsuleConfiguration` will be created: please, refer to the Helm Chart values to pick up your desired settings. @@ -52,9 +52,9 @@ Unfortunately, Helm doesn't manage the lifecycle of Custom Resource Definitions, This process must be executed manually as follows: ``` -kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.0/config/crd/bases/globaltenantresources-crd.yaml -kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.0/config/crd/bases/tenant-crd.yaml -kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.0/config/crd/bases/tenantresources-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/config/crd/bases/globaltenantresources-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/config/crd/bases/tenant-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/config/crd/bases/tenantresources-crd.yaml ``` > We're giving for granted that Capsule is installed in the `capsule-system` Namespace. @@ -76,7 +76,7 @@ Ensure to update the Capsule repository to fetch the latest changes. helm repo update ``` -The latest Chart must be used, at the current time, 0.3.0 is expected for Capsule v0.2.0, you can fetch the full list of available charts with the following command. +The latest Chart must be used, at the current time, >0.3.0 is expected for Capsule >v0.2.0, you can fetch the full list of available charts with the following command. ``` helm search repo -l clastix/capsule From ee813c5343e9bb1850f41d56ee7923f170b7d8db Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 31 Jan 2023 17:00:43 +0100 Subject: [PATCH 082/153] chore(kustomize): bumping up to v0.2.1 --- config/install.yaml | 2 +- config/manager/kustomization.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/install.yaml b/config/install.yaml index ab461c92..7883dd9c 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -2767,7 +2767,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: clastix/capsule:v0.2.0 + image: clastix/capsule:v0.2.1 imagePullPolicy: IfNotPresent name: manager ports: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index e195ab67..0bab283a 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -7,4 +7,4 @@ kind: Kustomization images: - name: controller newName: clastix/capsule - newTag: v0.2.0 + newTag: v0.2.1 From d92f1e7825d3fc09f9492bf494057d22cafc71c1 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 31 Jan 2023 17:01:21 +0100 Subject: [PATCH 083/153] chore(helm): bumping up to v0.2.1 --- charts/capsule/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 63929b82..03907e14 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,8 +21,8 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.3.0 +version: 0.3.1 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. -appVersion: 0.2.0 +appVersion: 0.2.1 From c059d503d0a500cd42d2e4914bb046cb8290d408 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 7 Feb 2023 14:17:36 +0100 Subject: [PATCH 084/153] refactor(kustomize): removing unrequired RBAC for metrics --- config/prometheus/kustomization.yaml | 2 -- config/prometheus/role.yaml | 18 ------------------ config/prometheus/rolebinding.yaml | 15 --------------- 3 files changed, 35 deletions(-) delete mode 100644 config/prometheus/role.yaml delete mode 100644 config/prometheus/rolebinding.yaml diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml index 4e44d90b..ed137168 100644 --- a/config/prometheus/kustomization.yaml +++ b/config/prometheus/kustomization.yaml @@ -1,4 +1,2 @@ resources: - monitor.yaml -- role.yaml -- rolebinding.yaml diff --git a/config/prometheus/role.yaml b/config/prometheus/role.yaml deleted file mode 100644 index cc789363..00000000 --- a/config/prometheus/role.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - control-plane: controller-manager - name: capsule-metrics-role - namespace: capsule-system -rules: -- apiGroups: - - "" - resources: - - services - - endpoints - - pods - verbs: - - get - - list - - watch diff --git a/config/prometheus/rolebinding.yaml b/config/prometheus/rolebinding.yaml deleted file mode 100644 index b5c57a56..00000000 --- a/config/prometheus/rolebinding.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - control-plane: controller-manager - name: capsule-metrics-rolebinding - namespace: system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: capsule-metrics-role -subjects: -- kind: ServiceAccount - name: capsule - namespace: capsule-system From 930f0382d102ae02145d99756b93e6218da676e4 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 7 Feb 2023 14:15:11 +0100 Subject: [PATCH 085/153] refactor(helm): removing unrequired RBAC for metrics Providing the required RBAC is not part of the Capsule scope, rather, it should be address by the Prometheus setup. Reference: https://github.com/clastix/capsule/issues/696#issuecomment-1420611891 --- charts/capsule/Chart.yaml | 2 +- charts/capsule/README.md | 2 - charts/capsule/templates/metrics-rbac.yaml | 46 ---------------------- charts/capsule/values.yaml | 5 --- 4 files changed, 1 insertion(+), 54 deletions(-) delete mode 100644 charts/capsule/templates/metrics-rbac.yaml diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 03907e14..a73dea5f 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,7 +21,7 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.3.1 +version: 0.3.2 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. diff --git a/charts/capsule/README.md b/charts/capsule/README.md index fe55ee9f..a84dfaff 100644 --- a/charts/capsule/README.md +++ b/charts/capsule/README.md @@ -119,8 +119,6 @@ Here the values you can override: | serviceMonitor.labels | object | `{}` | Assign additional labels according to Prometheus' serviceMonitorSelector matching labels | | serviceMonitor.matchLabels | object | `{}` | Change matching labels | | serviceMonitor.namespace | string | `""` | Install the ServiceMonitor into a different Namespace, as the monitoring stack one (default: the release one) | -| serviceMonitor.serviceAccount.name | string | `"capsule"` | ServiceAccount for Metrics RBAC | -| serviceMonitor.serviceAccount.namespace | string | `"capsule-system"` | ServiceAccount Namespace for Metrics RBAC | | serviceMonitor.targetLabels | list | `[]` | Set targetLabels for the serviceMonitor | ### Webhook Parameters diff --git a/charts/capsule/templates/metrics-rbac.yaml b/charts/capsule/templates/metrics-rbac.yaml deleted file mode 100644 index 3e694358..00000000 --- a/charts/capsule/templates/metrics-rbac.yaml +++ /dev/null @@ -1,46 +0,0 @@ -{{- if .Values.serviceMonitor.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - {{- include "capsule.labels" . | nindent 4 }} - {{- if .Values.serviceMonitor.labels }} - {{- toYaml .Values.serviceMonitor.labels | nindent 4 }} - {{- end }} - {{- with .Values.customAnnotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} - name: {{ include "capsule.fullname" . }}-metrics-role - namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }} -rules: -- apiGroups: - - "" - resources: - - services - - endpoints - - pods - verbs: - - get - - list - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - {{- include "capsule.labels" . | nindent 4 }} - {{- if .Values.serviceMonitor.labels }} - {{- toYaml .Values.serviceMonitor.labels | nindent 4 }} - {{- end }} - name: {{ include "capsule.fullname" . }}-metrics-rolebinding - namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ include "capsule.fullname" . }}-metrics-role -subjects: -- kind: ServiceAccount - name: {{ .Values.serviceMonitor.serviceAccount.name }} - namespace: {{ .Values.serviceMonitor.serviceAccount.namespace | default .Release.Namespace }} -{{- end }} diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index 000cee59..0023f1f9 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -212,11 +212,6 @@ serviceMonitor: matchLabels: {} # -- Set targetLabels for the serviceMonitor targetLabels: [] - serviceAccount: - # -- ServiceAccount for Metrics RBAC - name: capsule - # -- ServiceAccount Namespace for Metrics RBAC - namespace: capsule-system endpoint: # -- Set the scrape interval for the endpoint of the serviceMonitor interval: "15s" From ff17c8b99d43797a6991d6847bded76e1e889821 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Feb 2023 18:45:43 +0100 Subject: [PATCH 086/153] build(deps): bump go-restful Bumps [github.com/emicklei/go-restful](https://github.com/emicklei/go-restful) from 2.15.0+incompatible to 2.16.0+incompatible. - [Release notes](https://github.com/emicklei/go-restful/releases) - [Changelog](https://github.com/emicklei/go-restful/blob/v3/CHANGES.md) - [Commits](https://github.com/emicklei/go-restful/compare/v2.15.0...v2.16.0) --- updated-dependencies: - dependency-name: github.com/emicklei/go-restful dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index daf6c332..129ec2d3 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful v2.15.0+incompatible // indirect + github.com/emicklei/go-restful v2.16.0+incompatible // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-logr/zapr v1.2.0 // indirect diff --git a/go.sum b/go.sum index 6888b749..1d53d91b 100644 --- a/go.sum +++ b/go.sum @@ -127,8 +127,8 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= -github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.16.0+incompatible h1:rgqiKNjTnFQA6kkhFe16D8epTksy9HQ1MyrbDXSdYhM= +github.com/emicklei/go-restful v2.16.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= From 770ad22170d5484de7093bdcf8aa27178e9e95fe Mon Sep 17 00:00:00 2001 From: Vladimir <31961982+zvlb@users.noreply.github.com> Date: Fri, 10 Feb 2023 13:52:41 +0200 Subject: [PATCH 087/153] feat(helm): add control for securityContext Signed-off-by: Zemtsov Vladimir Co-authored-by: Zemtsov Vladimir --- charts/capsule/Chart.yaml | 2 +- charts/capsule/README.md | 2 ++ charts/capsule/templates/daemonset.yaml | 6 +++++- charts/capsule/templates/deployment.yaml | 6 +++++- charts/capsule/values.yaml | 17 +++++++++++++++++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index a73dea5f..f3adde84 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,7 +21,7 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.3.2 +version: 0.3.3 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. diff --git a/charts/capsule/README.md b/charts/capsule/README.md index a84dfaff..43b7f5e6 100644 --- a/charts/capsule/README.md +++ b/charts/capsule/README.md @@ -72,9 +72,11 @@ Here the values you can override: | mutatingWebhooksTimeoutSeconds | int | `30` | Timeout in seconds for mutating webhooks | | nodeSelector | object | `{}` | Set the node selector for the Capsule pod | | podAnnotations | object | `{}` | Annotations to add to the capsule pod. | +| podSecurityContext | object | `{"runAsGroup":1002,"runAsNonRoot":true,"runAsUser":1002,"seccompProfile":{"type":"RuntimeDefault"}}` | Set the securityContext for the Capsule pod | | podSecurityPolicy.enabled | bool | `false` | Specify if a Pod Security Policy must be created | | priorityClassName | string | `""` | Set the priority class name of the Capsule pod | | replicaCount | int | `1` | Set the replica count for capsule pod | +| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true}` | Set the securityContext for the Capsule container | | serviceAccount.annotations | object | `{}` | Annotations to add to the service account. | | serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | | serviceAccount.name | string | `"capsule"` | The name of the service account to use. If not set and `serviceAccount.create=true`, a name is generated using the fullname template | diff --git a/charts/capsule/templates/daemonset.yaml b/charts/capsule/templates/daemonset.yaml index 293442b8..194381b7 100644 --- a/charts/capsule/templates/daemonset.yaml +++ b/charts/capsule/templates/daemonset.yaml @@ -29,6 +29,10 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "capsule.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} {{- if .Values.manager.hostNetwork }} hostNetwork: true dnsPolicy: ClusterFirstWithHostNet @@ -84,5 +88,5 @@ spec: resources: {{- toYaml .Values.manager.resources | nindent 12 }} securityContext: - allowPrivilegeEscalation: false + {{- toYaml .Values.securityContext | nindent 12 }} {{- end }} diff --git a/charts/capsule/templates/deployment.yaml b/charts/capsule/templates/deployment.yaml index c0a6ebfb..fa70d591 100644 --- a/charts/capsule/templates/deployment.yaml +++ b/charts/capsule/templates/deployment.yaml @@ -28,6 +28,10 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "capsule.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} {{- if .Values.manager.hostNetwork }} hostNetwork: true dnsPolicy: ClusterFirstWithHostNet @@ -83,5 +87,5 @@ spec: resources: {{- toYaml .Values.manager.resources | nindent 12 }} securityContext: - allowPrivilegeEscalation: false + {{- toYaml .Values.securityContext | nindent 12 }} {{- end }} diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index 0023f1f9..a8425cbb 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -77,6 +77,23 @@ podAnnotations: {} # -- Set the priority class name of the Capsule pod priorityClassName: '' # system-cluster-critical +# -- Set the securityContext for the Capsule pod +podSecurityContext: + seccompProfile: + type: "RuntimeDefault" + runAsGroup: 1002 + runAsNonRoot: true + runAsUser: 1002 + + +# -- Set the securityContext for the Capsule container +securityContext: + capabilities: + drop: + - ALL + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + # -- Set the node selector for the Capsule pod nodeSelector: {} # node-role.kubernetes.io/master: "" From fe4954f39e4c190b27214f62e1e42ce01c559bb4 Mon Sep 17 00:00:00 2001 From: Zemtsov Vladimir Date: Tue, 14 Feb 2023 17:49:01 +0200 Subject: [PATCH 088/153] feat(helm): add securityContexts to jobs Signed-off-by: Zemtsov Vladimir --- charts/capsule/Chart.yaml | 2 +- charts/capsule/templates/post-install-job.yaml | 6 ++++++ charts/capsule/templates/pre-delete-job.yaml | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index f3adde84..2e8a24cf 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,7 +21,7 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.3.3 +version: 0.3.4 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. diff --git a/charts/capsule/templates/post-install-job.yaml b/charts/capsule/templates/post-install-job.yaml index 1e043a8e..58bb8786 100644 --- a/charts/capsule/templates/post-install-job.yaml +++ b/charts/capsule/templates/post-install-job.yaml @@ -45,5 +45,11 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} serviceAccountName: {{ include "capsule.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} diff --git a/charts/capsule/templates/pre-delete-job.yaml b/charts/capsule/templates/pre-delete-job.yaml index 0897bb1a..fe3a9075 100644 --- a/charts/capsule/templates/pre-delete-job.yaml +++ b/charts/capsule/templates/pre-delete-job.yaml @@ -47,4 +47,10 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} serviceAccountName: {{ include "capsule.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} From 7d1772031c1fca851d624bf45af922e235e8d287 Mon Sep 17 00:00:00 2001 From: Zemtsov Vladimir Date: Wed, 15 Feb 2023 12:25:21 +0200 Subject: [PATCH 089/153] feat: add bash script for local-test capsule Signed-off-by: Zemtsov Vladimir --- hack/local-test-with-kind.sh | 145 +++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100755 hack/local-test-with-kind.sh diff --git a/hack/local-test-with-kind.sh b/hack/local-test-with-kind.sh new file mode 100755 index 00000000..30d6f2fb --- /dev/null +++ b/hack/local-test-with-kind.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash + +# This script test capsule with kind +# Good to use it before pull request + +USER=alice +TENANT=oil +GROUP=capsule.clastix.io +KIND_CLUSTER_NAME=capsule-local-test + +function error_action() { + cleanup_action + exit 1 +} + +function cleanup_action() { + kind delete cluster --name=${KIND_CLUSTER_NAME} + rm -f ./tenant-test.yaml + rm -f ${USER}-${TENANT}.crt + rm -f ${USER}-${TENANT}.key + rm -f ${USER}-${TENANT}.kubeconfig +} + +function check_command() { + local command=$1 + + if ! command -v $command &> /dev/null; then + echo "Error: ${command} not found" + exit 1 + fi +} + +check_command kind +check_command kubectl + +### Prepare Kind cluster + +echo `date`": INFO: Create Kind Cluster" +error_create_kind=$(kind create cluster --name=${KIND_CLUSTER_NAME} 2>&1) +if [ $? -ne 0 ]; then + echo `date`": $error_create_kind" + exit 1 +fi + +echo `date`": INFO: Wait then Kind cluster be ready. Wait only 30 seconds" +counter=0 +while true +do + if [ $counter == 30 ]; then + echo `date`": ERROR: Kind cluster not ready for too long" + error_action + fi + + kubectl get nodes | grep " Ready " &>/dev/null + if [ $? == 0 ]; then + break + fi + + ((counter++)) + sleep 1 +done + +echo `date`": INFO: Kind cluster ready" + +### Install helm capsule to Kind + +echo `date`": INFO: Install helm capsule" +error_install_helm=$(helm install capsule ./charts/capsule/ -n capsule-system --create-namespace 2>&1) +if [ $? -ne 0 ]; then + echo `date`": $error_install_helm" + exit 1 +fi + +echo `date`": INFO: Wait then capsule POD be ready. Wait only 30 seconds" +counter=0 +while true +do + if [ $counter == 30 ]; then + echo `date`": ERROR: Kind cluster not ready for too long" + error_action + fi + + kubectl get pod -n capsule-system | grep " Running " &>/dev/null + if [ $? == 0 ]; then + break + fi + + ((counter++)) + sleep 1 +done +sleep 5 + +echo `date`": INFO: Capsule ready" + +### Tests + +echo `date`": INFO: Create tenant" +cat >>./tenant-test.yaml<&1) +if [ $? -ne 0 ]; then + echo `date`": $error_create_tenant" + error_action +fi + +echo `date`": INFO: Check tenant exist" +error_check_tenant=$(kubectl get tenant ${TENANT} 2>&1) +if [ $? -ne 0 ]; then + echo `date`": ERROR: $error_check_tenant" + error_action +fi + +echo `date`": INFO: Create user ${USER} for tenant ${TENANT}" +error_create_user=$(./hack/create-user.sh ${USER} ${TENANT} 2>&1) +if [ $? -ne 0 ]; then + echo `date`": ERROR: $error_create_user" + error_action +fi + +echo `date`": INFO: Create namespace from tenant user" +error_create_namespace=$(kubectl --kubeconfig=${USER}-${TENANT}.kubeconfig create ns ${TENANT}-test 2>&1) +if [ $? -ne 0 ]; then + echo `date`": ERROR: $error_create_namespace" + error_action +fi + +echo `date`": INFO: Check namespace exist in tenant" +error_tenant=$(kubectl get tenant ${TENANT} -o yaml | grep namespaces -A1 | grep ${TENANT}-test 2>&1) +if [ $? -ne 0 ]; then + echo `date`": ERROR: $error_tenant" + error_action +fi + +echo `date`": INFO: All ok" + +cleanup_action \ No newline at end of file From d63a9a0ca6723ca4513d5783ea5e062295a171d0 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 2 Feb 2023 18:48:57 +0100 Subject: [PATCH 090/153] fix: creation of namespaced resources backed by cache --- controllers/resources/processor.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go index 6e381c7c..bd86905f 100644 --- a/controllers/resources/processor.go +++ b/controllers/resources/processor.go @@ -225,7 +225,12 @@ func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstru ns := item.GetName() errGroup.Go(func() (err error) { - actual, desired := obj.DeepCopy(), obj.DeepCopy() + actual, desired := &unstructured.Unstructured{}, obj.DeepCopy() + + actual.SetAPIVersion(desired.GetAPIVersion()) + actual.SetKind(desired.GetKind()) + actual.SetNamespace(desired.GetNamespace()) + actual.SetName(desired.GetName()) // Using a deferred function to properly log the results, and adding the item to the processed set. defer func() { keysAndValues := []interface{}{"resource", fmt.Sprintf("%s/%s", ns, desired.GetName())} From 4e5c00fa658027860d5e1369db7fedc13f54fd2b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 2 Feb 2023 19:56:52 +0100 Subject: [PATCH 091/153] refactor: optimizing processing of tenant resources per namespace --- controllers/resources/processor.go | 209 +++++++++++++---------------- 1 file changed, 95 insertions(+), 114 deletions(-) diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go index bd86905f..1d7c4483 100644 --- a/controllers/resources/processor.go +++ b/controllers/resources/processor.go @@ -130,153 +130,134 @@ func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant syncErr := new(multierror.Error) - for nsIndex, item := range spec.NamespacedItems { - keysAndValues := []interface{}{"index", nsIndex, "namespace", item.Namespace} - // A TenantResource is created by a TenantOwner, and potentially, they could point to a resource in a non-owned - // Namespace: this must be blocked by checking it this is the case. - if !allowCrossNamespaceSelection && !tntNamespaces.Has(item.Namespace) { - log.Info("skipping processing of namespacedItem, referring a Namespace that is not part of the given Global", keysAndValues...) + codecFactory := serializer.NewCodecFactory(r.client.Scheme()) - continue - } - // Namespaced Items are relying on selecting resources, rather than specifying a specific name: - // creating it to get used by the client List action. - itemSelector, selectorErr := metav1.LabelSelectorAsSelector(&item.Selector) - if err != nil { - log.Error(selectorErr, "cannot create Selector for namespacedItem", keysAndValues...) + for _, ns := range namespaces.Items { + for nsIndex, item := range spec.NamespacedItems { + keysAndValues := []interface{}{"index", nsIndex, "namespace", item.Namespace} + // A TenantResource is created by a TenantOwner, and potentially, they could point to a resource in a non-owned + // Namespace: this must be blocked by checking it this is the case. + if !allowCrossNamespaceSelection && !tntNamespaces.Has(item.Namespace) { + log.Info("skipping processing of namespacedItem, referring a Namespace that is not part of the given Tenant", keysAndValues...) - continue - } + continue + } + // Namespaced Items are relying on selecting resources, rather than specifying a specific name: + // creating it to get used by the client List action. + itemSelector, selectorErr := metav1.LabelSelectorAsSelector(&item.Selector) + if selectorErr != nil { + log.Error(selectorErr, "cannot create Selector for namespacedItem", keysAndValues...) - objs := unstructured.UnstructuredList{} - objs.SetGroupVersionKind(schema.FromAPIVersionAndKind(item.APIVersion, fmt.Sprintf("%sList", item.Kind))) + syncErr = multierror.Append(syncErr, selectorErr) - if clientErr := r.client.List(ctx, &objs, client.InNamespace(item.Namespace), client.MatchingLabelsSelector{Selector: itemSelector}); clientErr != nil { - log.Error(clientErr, "cannot retrieve object for namespacedItem", keysAndValues...) + continue + } - syncErr = multierror.Append(syncErr, clientErr) + objs := unstructured.UnstructuredList{} + objs.SetGroupVersionKind(schema.FromAPIVersionAndKind(item.APIVersion, fmt.Sprintf("%sList", item.Kind))) - continue - } + if clientErr := r.client.List(ctx, &objs, client.InNamespace(item.Namespace), client.MatchingLabelsSelector{Selector: itemSelector}); clientErr != nil { + log.Error(clientErr, "cannot retrieve object for namespacedItem", keysAndValues...) - multiErr := new(multierror.Group) - // Iterating over all the retrieved objects from the resource spec to get replicated in all the selected Namespaces: - // in case of error during the create or update function, this will be appended to the list of errors. - for _, o := range objs.Items { - obj := o + syncErr = multierror.Append(syncErr, clientErr) - multiErr.Go(func() error { - nsItems, nsErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) - if nsErr != nil { - log.Error(err, "unable to sync namespacedItems", keysAndValues...) + continue + } - return nsErr - } + multiErr := new(multierror.Group) + // Iterating over all the retrieved objects from the resource spec to get replicated in all the selected Namespaces: + // in case of error during the create or update function, this will be appended to the list of errors. + for _, o := range objs.Items { + obj := o + obj.SetNamespace(ns.Name) - processed.Insert(nsItems...) + multiErr.Go(func() error { + kv := keysAndValues + kv = append(kv, []interface{}{"resource", fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetNamespace())}) - return nil - }) - } + if opErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations); opErr != nil { + log.Error(opErr, "unable to sync namespacedItems", kv...) - if objsErr := multiErr.Wait(); objsErr != nil { - syncErr = multierror.Append(syncErr, objsErr) - } - } + return opErr + } - codecFactory := serializer.NewCodecFactory(r.client.Scheme()) + log.Info("resource has been replicated", kv...) - for rawIndex, item := range spec.RawItems { - obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} + replicatedItem := &capsulev1beta2.ObjectReferenceStatus{} + replicatedItem.Name = obj.GetName() + replicatedItem.Kind = obj.GetKind() + replicatedItem.Namespace = ns.Name + replicatedItem.APIVersion = obj.GetAPIVersion() - if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(item.Raw, nil, &obj); decodeErr != nil { - log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) + processed.Insert(replicatedItem.String()) - syncErr = multierror.Append(syncErr, decodeErr) + return nil + }) + } - continue + if objsErr := multiErr.Wait(); objsErr != nil { + syncErr = multierror.Append(syncErr, objsErr) + } } - syncedRaw, rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations, namespaces) - if rawErr != nil { - log.Info("unable to sync rawItem", keysAndValues...) - // In case of error processing an item in one of any selected Namespaces, storing it to report it lately - // to the upper call to ensure a partial sync that will be fixed by a subsequent reconciliation. - syncErr = multierror.Append(syncErr, rawErr) - } else { - processed.Insert(syncedRaw...) - } - } + for rawIndex, item := range spec.RawItems { + obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} - return processed.List(), syncErr.ErrorOrNil() -} - -// createOrUpdate replicates the provided unstructured object to all the provided Namespaces: -// this function mimics the CreateOrUpdate, by retrieving the object to understand if it must be created or updated, -// along adding the additional metadata, if required. -func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string, annotations map[string]string, namespaces corev1.NamespaceList) ([]string, error) { - log := ctrllog.FromContext(ctx) + if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(item.Raw, nil, &obj); decodeErr != nil { + log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) - errGroup := new(multierror.Group) + syncErr = multierror.Append(syncErr, decodeErr) - var items []string - - for _, item := range namespaces.Items { - ns := item.GetName() - - errGroup.Go(func() (err error) { - actual, desired := &unstructured.Unstructured{}, obj.DeepCopy() - - actual.SetAPIVersion(desired.GetAPIVersion()) - actual.SetKind(desired.GetKind()) - actual.SetNamespace(desired.GetNamespace()) - actual.SetName(desired.GetName()) - // Using a deferred function to properly log the results, and adding the item to the processed set. - defer func() { - keysAndValues := []interface{}{"resource", fmt.Sprintf("%s/%s", ns, desired.GetName())} - - if err != nil { - log.Error(err, "unable to replicate resource", keysAndValues...) + continue + } - return - } + obj.SetNamespace(ns.Name) + if rawErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations); rawErr != nil { + log.Info("unable to sync rawItem", keysAndValues...) + // In case of error processing an item in one of any selected Namespaces, storing it to report it lately + // to the upper call to ensure a partial sync that will be fixed by a subsequent reconciliation. + syncErr = multierror.Append(syncErr, rawErr) + } else { log.Info("resource has been replicated", keysAndValues...) - replicatedItem := &capsulev1beta2.ObjectReferenceStatus{ - Name: obj.GetName(), - } + replicatedItem := &capsulev1beta2.ObjectReferenceStatus{} + replicatedItem.Name = obj.GetName() replicatedItem.Kind = obj.GetKind() - replicatedItem.Namespace = ns + replicatedItem.Namespace = ns.Name replicatedItem.APIVersion = obj.GetAPIVersion() - items = append(items, replicatedItem.String()) - }() + processed.Insert(replicatedItem.String()) + } + } + } - actual.SetNamespace(ns) + return processed.List(), syncErr.ErrorOrNil() +} - _, err = controllerutil.CreateOrUpdate(ctx, r.client, actual, func() error { - UID := actual.GetUID() - rv := actual.GetResourceVersion() +// createOrUpdate replicates the provided unstructured object to all the provided Namespaces: +// this function mimics the CreateOrUpdate, by retrieving the object to understand if it must be created or updated, +// along adding the additional metadata, if required. +func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstructured, labels map[string]string, annotations map[string]string) (err error) { + actual, desired := &unstructured.Unstructured{}, obj.DeepCopy() - actual.SetUnstructuredContent(desired.Object) - actual.SetNamespace(ns) - actual.SetLabels(labels) - actual.SetAnnotations(annotations) - actual.SetResourceVersion(rv) - actual.SetUID(UID) + actual.SetAPIVersion(desired.GetAPIVersion()) + actual.SetKind(desired.GetKind()) + actual.SetNamespace(desired.GetNamespace()) + actual.SetName(desired.GetName()) - return nil - }) + _, err = controllerutil.CreateOrUpdate(ctx, r.client, actual, func() error { + UID := actual.GetUID() + rv := actual.GetResourceVersion() - return - }) - } - // Wait returns *multierror.Error that implements stdlib error: - // the nil check must be performed down here rather than at the caller level to avoid wrong casting. - if err := errGroup.Wait(); err != nil { - return items, err - } + actual.SetUnstructuredContent(desired.Object) + actual.SetLabels(labels) + actual.SetAnnotations(annotations) + actual.SetResourceVersion(rv) + actual.SetUID(UID) + + return nil + }) - return items, nil + return err } From 8e7078ad4f905e03ef67767c4cd55455e0d712f5 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 2 Feb 2023 20:07:46 +0100 Subject: [PATCH 092/153] feat: template support for rawitems Allowed template values: - `{{ tenant.name }}` for the Tenant name managing the Namespace - `{{ namespace }}` for the Namespace where the resource is replicated --- controllers/resources/processor.go | 12 +++++++++++- go.mod | 2 ++ go.sum | 4 ++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go index 1d7c4483..1db2b28a 100644 --- a/controllers/resources/processor.go +++ b/controllers/resources/processor.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/hashicorp/go-multierror" + "github.com/valyala/fasttemplate" corev1 "k8s.io/api/core/v1" apierr "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -201,9 +202,18 @@ func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant } for rawIndex, item := range spec.RawItems { + template := string(item.Raw) + + t := fasttemplate.New(template, "{{ ", " }}") + + tmplString := t.ExecuteString(map[string]interface{}{ + "tenant.name": tnt.Name, + "namespace": ns.Name, + }) + obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} - if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode(item.Raw, nil, &obj); decodeErr != nil { + if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode([]byte(tmplString), nil, &obj); decodeErr != nil { log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) syncErr = multierror.Append(syncErr, decodeErr) diff --git a/go.mod b/go.mod index 129ec2d3..2ba05ac3 100644 --- a/go.mod +++ b/go.mod @@ -59,6 +59,8 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect diff --git a/go.sum b/go.sum index 1d53d91b..ad95a2b0 100644 --- a/go.sum +++ b/go.sum @@ -482,6 +482,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From 018784564a65b1a8211462b13f9343c5a4863afa Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 2 Feb 2023 20:09:20 +0100 Subject: [PATCH 093/153] test(e2e): template support for rawitems --- e2e/tenantresource_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/e2e/tenantresource_test.go b/e2e/tenantresource_test.go index a1d7cede..76847f82 100644 --- a/e2e/tenantresource_test.go +++ b/e2e/tenantresource_test.go @@ -104,6 +104,10 @@ var _ = Describe("Creating a TenantResource object", func() { Name: "raw-secret-1", }, Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "{{ tenant.name }}": []byte("Cg=="), + "{{ namespace }}": []byte("Cg=="), + }, }, }, }, @@ -118,6 +122,10 @@ var _ = Describe("Creating a TenantResource object", func() { Name: "raw-secret-2", }, Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "{{ tenant.name }}": []byte("Cg=="), + "{{ namespace }}": []byte("Cg=="), + }, }, }, }, @@ -132,6 +140,10 @@ var _ = Describe("Creating a TenantResource object", func() { Name: "raw-secret-3", }, Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "{{ tenant.name }}": []byte("Cg=="), + "{{ namespace }}": []byte("Cg=="), + }, }, }, }, @@ -220,6 +232,16 @@ var _ = Describe("Creating a TenantResource object", func() { return secrets.Items }, defaultTimeoutInterval, defaultPollInterval).Should(HaveLen(4)) }) + + By(fmt.Sprintf("ensuring raw items are templated in %s Namespace", ns), func() { + for _, name := range []string{"raw-secret-1", "raw-secret-2", "raw-secret-3"} { + secret := corev1.Secret{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: ns}, &secret)).ToNot(HaveOccurred()) + + Expect(secret.Data).To(HaveKey(solar.Name)) + Expect(secret.Data).To(HaveKey(ns)) + } + }) } By("using a Namespace selector", func() { From 610a03d0b91b93809055e436d2134942a8447f65 Mon Sep 17 00:00:00 2001 From: Zemtsov Vladimir Date: Thu, 16 Feb 2023 11:32:05 +0200 Subject: [PATCH 094/153] fix(helm): move imagePullSecrets to root values Signed-off-by: Zemtsov Vladimir --- charts/capsule/Chart.yaml | 2 +- charts/capsule/README.md | 2 +- charts/capsule/values.yaml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 2e8a24cf..76161e5a 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,7 +21,7 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.3.4 +version: 0.3.5 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. diff --git a/charts/capsule/README.md b/charts/capsule/README.md index 43b7f5e6..2211090e 100644 --- a/charts/capsule/README.md +++ b/charts/capsule/README.md @@ -66,6 +66,7 @@ Here the values you can override: | certManager.generateCertificates | bool | `false` | Specifies whether capsule webhooks certificates should be generated using cert-manager | | customAnnotations | object | `{}` | Additional annotations which will be added to all resources created by Capsule helm chart | | customLabels | object | `{}` | Additional labels which will be added to all resources created by Capsule helm chart | +| imagePullSecrets | list | `[]` | Configuration for `imagePullSecrets` so that you can use a private images registry. | | jobs.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy of the helm chart job | | jobs.image.repository | string | `"clastix/kubectl"` | Set the image repository of the helm chart job | | jobs.image.tag | string | `""` | Set the image tag of the helm chart job | @@ -94,7 +95,6 @@ Here the values you can override: | manager.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy. | | manager.image.repository | string | `"clastix/capsule"` | Set the image repository of the capsule. | | manager.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | -| manager.imagePullSecrets | list | `[]` | Configuration for `imagePullSecrets` so that you can use a private images registry. | | manager.kind | string | `"Deployment"` | Set the controller deployment mode as `Deployment` or `DaemonSet`. | | manager.livenessProbe | object | `{"httpGet":{"path":"/healthz","port":10080}}` | Configure the liveness probe using Deployment probe spec | | manager.options.capsuleUserGroups | list | `["capsule.clastix.io"]` | Override the Capsule user groups | diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index a8425cbb..e81da44b 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -25,9 +25,6 @@ manager: # -- Overrides the image tag whose default is the chart appVersion. tag: '' - # -- Configuration for `imagePullSecrets` so that you can use a private images registry. - imagePullSecrets: [] - # -- Specifies if the container should be started in hostNetwork mode. # # Required for use in some managed kubernetes clusters (such as AWS EKS) with custom @@ -68,6 +65,9 @@ manager: cpu: 200m memory: 128Mi +# -- Configuration for `imagePullSecrets` so that you can use a private images registry. +imagePullSecrets: [] + # -- Annotations to add to the capsule pod. podAnnotations: {} # The following annotations guarantee scheduling for critical add-on pods From f0fdab015b88200f43e0f3760c823e1f1351a2ac Mon Sep 17 00:00:00 2001 From: r3drun3 Date: Fri, 24 Feb 2023 12:54:50 +0100 Subject: [PATCH 095/153] docs(readme): add ci status badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 79467d4a..d7827fdd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@

+ From 3991359bfe0c09264d2c817f47b177b7cde28e64 Mon Sep 17 00:00:00 2001 From: Sagar Jadhav Date: Wed, 1 Mar 2023 13:47:55 +0530 Subject: [PATCH 096/153] chore(helm): bump up the version Signed-off-by: Sagar Jadhav --- charts/capsule/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 76161e5a..5ead442e 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,8 +21,8 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.3.5 +version: 0.3.6 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. -appVersion: 0.2.1 +appVersion: 0.2.2 From da78423f426cddc685370d479770a5a621244bd1 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 2 Mar 2023 11:30:50 +0100 Subject: [PATCH 097/153] fix: preventing index out of range when sa is impersonating --- pkg/webhook/utils/is_capsule_user.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/webhook/utils/is_capsule_user.go b/pkg/webhook/utils/is_capsule_user.go index aeb37476..ede65b11 100644 --- a/pkg/webhook/utils/is_capsule_user.go +++ b/pkg/webhook/utils/is_capsule_user.go @@ -28,9 +28,9 @@ func IsCapsuleUser(ctx context.Context, req admission.Request, clt client.Client if sets.NewString(req.UserInfo.Groups...).Has("system:serviceaccounts") { parts := strings.Split(req.UserInfo.Username, ":") - targetNamespace := parts[2] + if len(parts) == 4 { + targetNamespace := parts[2] - if len(targetNamespace) > 0 { tl := &capsulev1beta2.TenantList{} if err := clt.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", targetNamespace)}); err != nil { return false From 89348c9499b021d8dd84ee7c44c7bddd715de165 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 2 Mar 2023 14:10:14 +0100 Subject: [PATCH 098/153] chore(golangci-lint): updating to latest version and code alignement --- .github/workflows/ci.yml | 4 ++-- .golangci.yml | 9 ++++++++- Makefile | 4 ++-- api/v1alpha1/conversion_hub.go | 4 ++-- api/v1alpha1/conversion_hub_test.go | 2 +- api/v1beta1/tenant_types.go | 4 +--- api/v1beta2/tenant_types.go | 4 +--- controllers/resources/global.go | 2 +- controllers/resources/namespaced.go | 2 +- controllers/resources/processor.go | 4 ++-- controllers/tenant/limitranges.go | 3 +-- controllers/tenant/namespaces.go | 2 +- controllers/tenant/networkpolicies.go | 3 +-- controllers/tenant/resourcequotas.go | 5 ++--- controllers/tenant/resourcequotas_quota.go | 2 +- main.go | 2 +- pkg/cert/ca.go | 2 +- pkg/configuration/client.go | 2 +- pkg/indexer/ingress/hostname_path.go | 2 +- pkg/indexer/tenant/namespaces.go | 2 +- pkg/utils/user_group.go | 4 +--- pkg/webhook/ingress/errors.go | 2 +- pkg/webhook/ingress/types.go | 6 +++--- pkg/webhook/ingress/utils.go | 4 ++-- pkg/webhook/ingress/validate_collision.go | 4 ++-- pkg/webhook/namespace/errors.go | 2 +- pkg/webhook/namespace/user_metadata.go | 2 +- pkg/webhook/networkpolicy/validating.go | 2 +- pkg/webhook/node/errors.go | 2 +- pkg/webhook/pod/containerregistry.go | 2 +- pkg/webhook/pod/priorityclass.go | 21 --------------------- pkg/webhook/utils/is_capsule_user.go | 2 +- 32 files changed, 47 insertions(+), 70 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17c51753..cbbd6947 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,9 +24,9 @@ jobs: - name: Run golangci-lint uses: golangci/golangci-lint-action@v2.3.0 with: - version: v1.45.2 + version: v1.51.2 only-new-issues: false - args: --timeout 2m --config .golangci.yml + args: --timeout 5m --config .golangci.yml diff: name: diff runs-on: ubuntu-18.04 diff --git a/.golangci.yml b/.golangci.yml index b611560a..6adb72eb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -39,9 +39,16 @@ linters: - testpackage - varnamelen - wrapcheck + - exhaustruct + - varcheck + - structcheck + - nosnakecase + - deadcode + - ifshort + - nonamedreturns service: - golangci-lint-version: 1.45.2 + golangci-lint-version: 1.51.2 run: skip-files: diff --git a/Makefile b/Makefile index 1d1df373..bcb30b4f 100644 --- a/Makefile +++ b/Makefile @@ -240,7 +240,7 @@ goimports: GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint golangci-lint: ## Download golangci-lint locally if necessary. - $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.2) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.2) # Linting code as PR is expecting .PHONY: golint @@ -250,7 +250,7 @@ golint: golangci-lint # Running e2e tests in a KinD instance .PHONY: e2e e2e/%: ginkgo - $(MAKE) e2e-build/$* && $(MAKE) e2e-exec || $(MAKE) e2e-destroy + $(MAKE) e2e-build/$* && $(MAKE) e2e-exec && $(MAKE) e2e-destroy e2e-build/%: kind create cluster --wait=60s --name capsule --image=kindest/node:$* diff --git a/api/v1alpha1/conversion_hub.go b/api/v1alpha1/conversion_hub.go index fe636631..e482ebb7 100644 --- a/api/v1alpha1/conversion_hub.go +++ b/api/v1alpha1/conversion_hub.go @@ -133,7 +133,7 @@ func (in *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec { return owners } -// nolint:gocognit,gocyclo,cyclop,maintidx +//nolint:gocognit,gocyclo,cyclop,maintidx func (in *Tenant) ConvertTo(dstRaw conversion.Hub) error { dst, ok := dstRaw.(*capsulev1beta1.Tenant) if !ok { @@ -365,7 +365,7 @@ func (in *Tenant) ConvertTo(dstRaw conversion.Hub) error { return nil } -// nolint:gocognit,gocyclo,cyclop +//nolint:gocognit,gocyclo,cyclop func (in *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) { ownersAnnotations := map[string][]string{ ownerGroupsAnnotation: nil, diff --git a/api/v1alpha1/conversion_hub_test.go b/api/v1alpha1/conversion_hub_test.go index f2d4e9ab..21c512dc 100644 --- a/api/v1alpha1/conversion_hub_test.go +++ b/api/v1alpha1/conversion_hub_test.go @@ -19,7 +19,7 @@ import ( "github.com/clastix/capsule/pkg/api" ) -// nolint:maintidx +//nolint:maintidx func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { var namespaceQuota int32 = 5 diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 29cfd5b4..5d809b24 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -75,9 +75,7 @@ func init() { func (in *Tenant) GetNamespaces() (res []string) { res = make([]string, 0, len(in.Status.Namespaces)) - for _, ns := range in.Status.Namespaces { - res = append(res, ns) - } + res = append(res, in.Status.Namespaces...) return } diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index c7a18f46..60edd2fe 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -76,9 +76,7 @@ type Tenant struct { func (in *Tenant) GetNamespaces() (res []string) { res = make([]string, 0, len(in.Status.Namespaces)) - for _, ns := range in.Status.Namespaces { - res = append(res, ns) - } + res = append(res, in.Status.Namespaces...) return } diff --git a/controllers/resources/global.go b/controllers/resources/global.go index 673cc367..7cdb8f4d 100644 --- a/controllers/resources/global.go +++ b/controllers/resources/global.go @@ -121,7 +121,7 @@ func (r *Global) reconcileNormal(ctx context.Context, tntResource *capsulev1beta } if tntResource.Status.ProcessedItems == nil { - tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, 0) + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0) } // Retrieving the list of the Tenants up to the selector provided by the GlobalTenantResource resource. diff --git a/controllers/resources/namespaced.go b/controllers/resources/namespaced.go index 92a2d1ca..68e6c03f 100644 --- a/controllers/resources/namespaced.go +++ b/controllers/resources/namespaced.go @@ -85,7 +85,7 @@ func (r *Namespaced) reconcileNormal(ctx context.Context, tntResource *capsulev1 // Adding the default value for the status if tntResource.Status.ProcessedItems == nil { - tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, 0) + tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0) } // Retrieving the parent of the Tenant Resource: diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go index 1db2b28a..b9fd2870 100644 --- a/controllers/resources/processor.go +++ b/controllers/resources/processor.go @@ -135,7 +135,7 @@ func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant for _, ns := range namespaces.Items { for nsIndex, item := range spec.NamespacedItems { - keysAndValues := []interface{}{"index", nsIndex, "namespace", item.Namespace} + keysAndValues := []any{"index", nsIndex, "namespace", item.Namespace} // A TenantResource is created by a TenantOwner, and potentially, they could point to a resource in a non-owned // Namespace: this must be blocked by checking it this is the case. if !allowCrossNamespaceSelection && !tntNamespaces.Has(item.Namespace) { @@ -174,7 +174,7 @@ func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant multiErr.Go(func() error { kv := keysAndValues - kv = append(kv, []interface{}{"resource", fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetNamespace())}) + kv = append(kv, "resource", fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetNamespace())) if opErr := r.createOrUpdate(ctx, &obj, objLabels, objAnnotations); opErr != nil { log.Error(opErr, "unable to sync namespacedItems", kv...) diff --git a/controllers/tenant/limitranges.go b/controllers/tenant/limitranges.go index bfa049ff..99a03576 100644 --- a/controllers/tenant/limitranges.go +++ b/controllers/tenant/limitranges.go @@ -17,9 +17,8 @@ import ( "github.com/clastix/capsule/pkg/utils" ) -// nolint:dupl // Ensuring all the LimitRange are applied to each Namespace handled by the Tenant. -func (r *Manager) syncLimitRanges(ctx context.Context, tenant *capsulev1beta2.Tenant) error { +func (r *Manager) syncLimitRanges(ctx context.Context, tenant *capsulev1beta2.Tenant) error { //nolint:dupl // getting requested LimitRange keys keys := make([]string, 0, len(tenant.Spec.LimitRanges.Items)) diff --git a/controllers/tenant/namespaces.go b/controllers/tenant/namespaces.go index e0ec2795..5ff976a4 100644 --- a/controllers/tenant/namespaces.go +++ b/controllers/tenant/namespaces.go @@ -42,7 +42,7 @@ func (r *Manager) syncNamespaces(ctx context.Context, tenant *capsulev1beta2.Ten return } -// nolint:gocognit +//nolint:gocognit func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, tnt *capsulev1beta2.Tenant) (err error) { var res controllerutil.OperationResult diff --git a/controllers/tenant/networkpolicies.go b/controllers/tenant/networkpolicies.go index 4d155fad..914897ee 100644 --- a/controllers/tenant/networkpolicies.go +++ b/controllers/tenant/networkpolicies.go @@ -17,9 +17,8 @@ import ( "github.com/clastix/capsule/pkg/utils" ) -// nolint:dupl // Ensuring all the NetworkPolicies are applied to each Namespace handled by the Tenant. -func (r *Manager) syncNetworkPolicies(ctx context.Context, tenant *capsulev1beta2.Tenant) error { +func (r *Manager) syncNetworkPolicies(ctx context.Context, tenant *capsulev1beta2.Tenant) error { //nolint:dupl // getting requested NetworkPolicy keys keys := make([]string, 0, len(tenant.Spec.NetworkPolicies.Items)) diff --git a/controllers/tenant/resourcequotas.go b/controllers/tenant/resourcequotas.go index 0f740040..23886322 100644 --- a/controllers/tenant/resourcequotas.go +++ b/controllers/tenant/resourcequotas.go @@ -36,8 +36,7 @@ import ( // the mutateFn along with the CreateOrUpdate to don't perform the update since resources are identical. // // In case of Namespace-scoped Resource Budget, we're just replicating the resources across all registered Namespaces. -// nolint:gocognit -func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta2.Tenant) (err error) { +func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta2.Tenant) (err error) { //nolint:gocognit // getting ResourceQuota labels for the mutateFn var tenantLabel, typeLabel string @@ -48,7 +47,7 @@ func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta2 if typeLabel, err = utils.GetTypeLabel(&corev1.ResourceQuota{}); err != nil { return err } - // nolint:nestif + //nolint:nestif if tenant.Spec.ResourceQuota.Scope == api.ResourceQuotaScopeTenant { group := new(errgroup.Group) diff --git a/controllers/tenant/resourcequotas_quota.go b/controllers/tenant/resourcequotas_quota.go index ac370a2a..55e42749 100644 --- a/controllers/tenant/resourcequotas_quota.go +++ b/controllers/tenant/resourcequotas_quota.go @@ -25,7 +25,7 @@ func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *cap group string version string } - // nolint:prealloc + //nolint:prealloc var resourceList []resource for k := range tenant.GetAnnotations() { diff --git a/main.go b/main.go index a69e8de4..f35de554 100644 --- a/main.go +++ b/main.go @@ -96,7 +96,7 @@ func newDelegatingClient(cache cache.Cache, config *rest.Config, options client. return delegatingClient, nil } -// nolint:maintidx,cyclop +//nolint:maintidx,cyclop func main() { var enableLeaderElection, version bool diff --git a/pkg/cert/ca.go b/pkg/cert/ca.go index cd676e83..d09fcf17 100644 --- a/pkg/cert/ca.go +++ b/pkg/cert/ca.go @@ -144,7 +144,7 @@ func NewCertificateAuthorityFromBytes(certBytes, keyBytes []byte) (*CapsuleCA, e }, nil } -// nolint:nakedret +//nolint:nakedret func (c *CapsuleCA) GenerateCertificate(opts CertificateOptions) (certificatePem *bytes.Buffer, certificateKey *bytes.Buffer, err error) { var certPrivKey *rsa.PrivateKey certPrivKey, err = rsa.GenerateKey(rand.Reader, 4096) diff --git a/pkg/configuration/client.go b/pkg/configuration/client.go index 75ca1aa5..c50d1b20 100644 --- a/pkg/configuration/client.go +++ b/pkg/configuration/client.go @@ -46,7 +46,7 @@ func NewCapsuleConfiguration(ctx context.Context, client client.Client, name str func (c *capsuleConfiguration) ProtectedNamespaceRegexp() (*regexp.Regexp, error) { expr := c.retrievalFn().Spec.ProtectedNamespaceRegexpString if len(expr) == 0 { - return nil, nil // nolint:nilnil + return nil, nil //nolint:nilnil } r, err := regexp.Compile(expr) diff --git a/pkg/indexer/ingress/hostname_path.go b/pkg/indexer/ingress/hostname_path.go index 459bfc26..2858614a 100644 --- a/pkg/indexer/ingress/hostname_path.go +++ b/pkg/indexer/ingress/hostname_path.go @@ -22,7 +22,7 @@ type HostnamePath struct { Obj metav1.Object } -// nolint:forcetypeassert +//nolint:forcetypeassert func (s HostnamePath) Object() client.Object { return s.Obj.(client.Object) } diff --git a/pkg/indexer/tenant/namespaces.go b/pkg/indexer/tenant/namespaces.go index d4388f92..ab9d8008 100644 --- a/pkg/indexer/tenant/namespaces.go +++ b/pkg/indexer/tenant/namespaces.go @@ -21,7 +21,7 @@ func (o NamespacesReference) Field() string { return ".status.namespaces" } -// nolint:forcetypeassert +//nolint:forcetypeassert func (o NamespacesReference) Func() client.IndexerFunc { return func(object client.Object) []string { return object.(api.Tenant).GetNamespaces() diff --git a/pkg/utils/user_group.go b/pkg/utils/user_group.go index 5aae517b..2db7e1e6 100644 --- a/pkg/utils/user_group.go +++ b/pkg/utils/user_group.go @@ -15,9 +15,7 @@ type userGroupList []string func NewUserGroupList(groups []string) UserGroupList { list := make(userGroupList, len(groups)) - for k, v := range groups { - list[k] = v - } + copy(list, groups) sort.SliceStable(list, func(i, j int) bool { return list[i] < list[j] diff --git a/pkg/webhook/ingress/errors.go b/pkg/webhook/ingress/errors.go index 040a0097..80d1a622 100644 --- a/pkg/webhook/ingress/errors.go +++ b/pkg/webhook/ingress/errors.go @@ -88,7 +88,7 @@ func (i ingressClassNotValidError) Error() string { return utils.DefaultAllowedValuesErrorMessage(i.spec, err) } -// nolint:predeclared +//nolint:predeclared func appendHostnameError(spec api.AllowedListSpec) (append string) { if len(spec.Exact) > 0 { append = fmt.Sprintf(", specify one of the following (%s)", strings.Join(spec.Exact, ", ")) diff --git a/pkg/webhook/ingress/types.go b/pkg/webhook/ingress/types.go index 717eeb45..bfc19a9a 100644 --- a/pkg/webhook/ingress/types.go +++ b/pkg/webhook/ingress/types.go @@ -63,7 +63,7 @@ func (n NetworkingV1) Namespace() string { return n.GetNamespace() } -// nolint:dupl +//nolint:dupl func (n NetworkingV1) HostnamePathsPairs() (pairs map[string]sets.String) { pairs = make(map[string]sets.String) @@ -129,7 +129,7 @@ func (n NetworkingV1Beta1) Namespace() string { return n.GetNamespace() } -// nolint:dupl +//nolint:dupl func (n NetworkingV1Beta1) HostnamePathsPairs() (pairs map[string]sets.String) { pairs = make(map[string]sets.String) @@ -193,7 +193,7 @@ func (e Extension) Namespace() string { return e.GetNamespace() } -// nolint:dupl +//nolint:dupl func (e Extension) HostnamePathsPairs() (pairs map[string]sets.String) { pairs = make(map[string]sets.String) diff --git a/pkg/webhook/ingress/utils.go b/pkg/webhook/ingress/utils.go index 80ad08b4..0fe7f99a 100644 --- a/pkg/webhook/ingress/utils.go +++ b/pkg/webhook/ingress/utils.go @@ -26,13 +26,13 @@ func TenantFromIngress(ctx context.Context, c client.Client, ingress Ingress) (* } if len(tenantList.Items) == 0 { - return nil, nil // nolint:nilnil + return nil, nil //nolint:nilnil } return &tenantList.Items[0], nil } -// nolint:nakedret +//nolint:nakedret func FromRequest(req admission.Request, decoder *admission.Decoder) (ingress Ingress, err error) { switch req.Kind.Group { case "networking.k8s.io": diff --git a/pkg/webhook/ingress/validate_collision.go b/pkg/webhook/ingress/validate_collision.go index 8f286a35..e552be81 100644 --- a/pkg/webhook/ingress/validate_collision.go +++ b/pkg/webhook/ingress/validate_collision.go @@ -84,7 +84,7 @@ func (r *collision) validate(ctx context.Context, client client.Client, req admi return &response } -// nolint:gocognit,gocyclo,cyclop +//nolint:gocognit,gocyclo,cyclop func (r *collision) validateCollision(ctx context.Context, clt client.Client, ing Ingress, scope api.HostnameCollisionScope) error { for hostname, paths := range ing.HostnamePathsPairs() { for path := range paths { @@ -100,7 +100,7 @@ func (r *collision) validateCollision(ctx context.Context, clt client.Client, in } namespaces := sets.NewString() - // nolint:exhaustive + //nolint:exhaustive switch scope { case api.HostnameCollisionScopeCluster: tenantList := &capsulev1beta2.TenantList{} diff --git a/pkg/webhook/namespace/errors.go b/pkg/webhook/namespace/errors.go index e28812e0..8b0ec4f2 100644 --- a/pkg/webhook/namespace/errors.go +++ b/pkg/webhook/namespace/errors.go @@ -10,7 +10,7 @@ import ( capsuleapi "github.com/clastix/capsule/pkg/api" ) -// nolint:predeclared +//nolint:predeclared func appendForbiddenError(spec *capsuleapi.ForbiddenListSpec) (append string) { append += "Forbidden are " if len(spec.Exact) > 0 { diff --git a/pkg/webhook/namespace/user_metadata.go b/pkg/webhook/namespace/user_metadata.go index 41b79d43..6d42f408 100644 --- a/pkg/webhook/namespace/user_metadata.go +++ b/pkg/webhook/namespace/user_metadata.go @@ -125,7 +125,7 @@ func (r *userMetadataHandler) OnUpdate(client client.Client, decoder *admission. } if v != oldNs.GetAnnotations()["scheduler.alpha.kubernetes.io/node-selector"] { - response := admission.Denied("the the node-selector annotation is enforced, cannot be updated") + response := admission.Denied("the node-selector annotation is enforced, cannot be updated") recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNodeSelectorUpdate", string(response.Result.Reason)) diff --git a/pkg/webhook/networkpolicy/validating.go b/pkg/webhook/networkpolicy/validating.go index c63d65ed..f5cc6b5a 100644 --- a/pkg/webhook/networkpolicy/validating.go +++ b/pkg/webhook/networkpolicy/validating.go @@ -50,7 +50,7 @@ func (r *handler) generic(ctx context.Context, req admission.Request, client cli return tnt, nil } - return nil, nil // nolint:nilnil + return nil, nil //nolint:nilnil } //nolint:dupl diff --git a/pkg/webhook/node/errors.go b/pkg/webhook/node/errors.go index 2bd22a85..1b7a5f37 100644 --- a/pkg/webhook/node/errors.go +++ b/pkg/webhook/node/errors.go @@ -10,7 +10,7 @@ import ( capsulev1beta2 "github.com/clastix/capsule/pkg/api" ) -// nolint:predeclared +//nolint:predeclared func appendForbiddenError(spec *capsulev1beta2.ForbiddenListSpec) (append string) { append += "Forbidden are " if len(spec.Exact) > 0 { diff --git a/pkg/webhook/pod/containerregistry.go b/pkg/webhook/pod/containerregistry.go index aa121d5d..c21e4cf0 100644 --- a/pkg/webhook/pod/containerregistry.go +++ b/pkg/webhook/pod/containerregistry.go @@ -86,7 +86,7 @@ func (h *containerRegistryHandler) VerifyContainerRegistry(recorder record.Event reg := NewRegistry(container.Image) if len(reg.Registry()) == 0 { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "MissingFQCI", "Pod %s/%s is not using using a fully qualified container image, cannot enforce registry the current Tenant", req.Namespace, req.Name, reg.Registry()) + recorder.Eventf(&tnt, corev1.EventTypeWarning, "MissingFQCI", "Pod %s/%s is not using a fully qualified container image, cannot enforce registry the current Tenant", req.Namespace, req.Name, reg.Registry()) response := admission.Denied(NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries).Error()) diff --git a/pkg/webhook/pod/priorityclass.go b/pkg/webhook/pod/priorityclass.go index d56ceb95..81d02eec 100644 --- a/pkg/webhook/pod/priorityclass.go +++ b/pkg/webhook/pod/priorityclass.go @@ -8,9 +8,6 @@ import ( "net/http" corev1 "k8s.io/api/core/v1" - schedulingv1 "k8s.io/api/scheduling/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -25,24 +22,6 @@ func PriorityClass() capsulewebhook.Handler { return &priorityClass{} } -func (h *priorityClass) class(ctx context.Context, c client.Client, name string) (client.Object, error) { - if len(name) == 0 { - return nil, nil - } - - obj := &schedulingv1.PriorityClass{} - - if err := c.Get(ctx, types.NamespacedName{Name: name}, obj); err != nil { - if errors.IsNotFound(err) { - return nil, nil - } - - return nil, err - } - - return obj, nil -} - func (h *priorityClass) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { pod := &corev1.Pod{} diff --git a/pkg/webhook/utils/is_capsule_user.go b/pkg/webhook/utils/is_capsule_user.go index ede65b11..bcd607c4 100644 --- a/pkg/webhook/utils/is_capsule_user.go +++ b/pkg/webhook/utils/is_capsule_user.go @@ -24,7 +24,7 @@ func IsCapsuleUser(ctx context.Context, req admission.Request, clt client.Client if groupList.Find("system:serviceaccounts:kube-system") { return false } - // nolint:nestif + //nolint:nestif if sets.NewString(req.UserInfo.Groups...).Has("system:serviceaccounts") { parts := strings.Split(req.UserInfo.Username, ":") From ac4f0ab6dd7ef44d6b4885e4f40f52ed2e68adc9 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 2 Mar 2023 14:24:39 +0100 Subject: [PATCH 099/153] fix(ci): allowing to run on k8s versions with no seccompprofile key --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index bcb30b4f..9b713617 100644 --- a/Makefile +++ b/Makefile @@ -266,6 +266,7 @@ e2e-build/%: --set "manager.image.tag=$(VERSION)" \ --set 'manager.livenessProbe.failureThreshold=10' \ --set 'manager.readinessProbe.failureThreshold=10' \ + --set 'podSecurityContext.seccompProfile=null' \ capsule \ ./charts/capsule From e64b3f8cf9f39ff3b40969435c0ba938c71cfb4b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 2 Mar 2023 14:34:17 +0100 Subject: [PATCH 100/153] chore(ci): dropped cgroupv2 support from k8s https://kind.sigs.k8s.io/docs/user/known-issues/#failure-to-create-cluster-with-cgroups-v2 --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 76a5db96..6ed88a03 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - k8s-version: ['v1.16.15', 'v1.17.11', 'v1.18.8', 'v1.19.4', 'v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.0'] + k8s-version: ['v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.1'] runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 From 92b1debe6b1778d756b5fe46737a3958a2a58957 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Feb 2023 01:06:33 +0000 Subject: [PATCH 101/153] build(deps): bump golang.org/x/net Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20220617184016-355a448f1bc9 to 0.7.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/commits/v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 14 ++++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 2ba05ac3..205ee39b 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.1 + github.com/valyala/fasttemplate v1.2.2 go.uber.org/zap v1.19.1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c k8s.io/api v0.24.2 @@ -60,14 +61,13 @@ require ( github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index ad95a2b0..db757e6b 100644 --- a/go.sum +++ b/go.sum @@ -638,8 +638,8 @@ golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -733,11 +733,12 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -746,8 +747,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 010ed41ca7ccebe5096175c4f2e28ccaf04b02bc Mon Sep 17 00:00:00 2001 From: Zadkiel Aharonian Date: Wed, 1 Mar 2023 08:53:44 +0000 Subject: [PATCH 102/153] feat(manager): allow customization of the webhook port --- main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index f35de554..4a35637e 100644 --- a/main.go +++ b/main.go @@ -102,8 +102,11 @@ func main() { var metricsAddr, namespace, configurationName string + var webhookPort int + var goFlagSet goflag.FlagSet + flag.IntVar(&webhookPort, "webhook-port", 9443, "The port the webhook server binds to.") flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, "Enable leader election for controller manager. "+ @@ -142,7 +145,7 @@ func main() { manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, - Port: 9443, + Port: webhookPort, LeaderElection: enableLeaderElection, LeaderElectionID: "42c733ea.clastix.capsule.io", HealthProbeBindAddress: ":10080", From 66f5f9010487e5f27131eeb22e7161f1e23ed1b4 Mon Sep 17 00:00:00 2001 From: Zadkiel Aharonian Date: Wed, 1 Mar 2023 08:53:55 +0000 Subject: [PATCH 103/153] feat(helm): allow customization of the webhook port --- charts/capsule/Chart.yaml | 2 +- charts/capsule/README.md | 1 + charts/capsule/templates/daemonset.yaml | 3 ++- charts/capsule/templates/deployment.yaml | 3 ++- charts/capsule/templates/webhook-service.yaml | 2 +- charts/capsule/values.yaml | 7 +++++++ 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 5ead442e..c1ae0934 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,7 +21,7 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.3.6 +version: 0.4.0 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. diff --git a/charts/capsule/README.md b/charts/capsule/README.md index 2211090e..2ad95348 100644 --- a/charts/capsule/README.md +++ b/charts/capsule/README.md @@ -107,6 +107,7 @@ Here the values you can override: | manager.resources.limits.memory | string | `"128Mi"` | | | manager.resources.requests.cpu | string | `"200m"` | | | manager.resources.requests.memory | string | `"128Mi"` | | +| manager.webhookPort | int | `9443` | Set an alternative to the default container port. Useful for use in some kubernetes clusters (such as GKE Private) with aggregator routing turned on, because pod ports have to be opened manually on the firewall side | ### ServiceMonitor Parameters diff --git a/charts/capsule/templates/daemonset.yaml b/charts/capsule/templates/daemonset.yaml index 194381b7..9266070e 100644 --- a/charts/capsule/templates/daemonset.yaml +++ b/charts/capsule/templates/daemonset.yaml @@ -60,6 +60,7 @@ spec: command: - /manager args: + - --webhook-port={{ .Values.manager.webhookPort }} - --enable-leader-election - --zap-log-level={{ default 4 .Values.manager.options.logLevel }} - --configuration-name=default @@ -72,7 +73,7 @@ spec: fieldPath: metadata.namespace ports: - name: webhook-server - containerPort: 9443 + containerPort: {{ .Values.manager.webhookPort }} protocol: TCP - name: metrics containerPort: 8080 diff --git a/charts/capsule/templates/deployment.yaml b/charts/capsule/templates/deployment.yaml index fa70d591..e0e6114e 100644 --- a/charts/capsule/templates/deployment.yaml +++ b/charts/capsule/templates/deployment.yaml @@ -59,6 +59,7 @@ spec: command: - /manager args: + - --webhook-port={{ .Values.manager.webhookPort }} - --enable-leader-election - --zap-log-level={{ default 4 .Values.manager.options.logLevel }} - --configuration-name=default @@ -71,7 +72,7 @@ spec: fieldPath: metadata.namespace ports: - name: webhook-server - containerPort: 9443 + containerPort: {{ .Values.manager.webhookPort }} protocol: TCP - name: metrics containerPort: 8080 diff --git a/charts/capsule/templates/webhook-service.yaml b/charts/capsule/templates/webhook-service.yaml index 3fceef91..c170e1cf 100644 --- a/charts/capsule/templates/webhook-service.yaml +++ b/charts/capsule/templates/webhook-service.yaml @@ -13,7 +13,7 @@ spec: - port: 443 name: https protocol: TCP - targetPort: 9443 + targetPort: {{ .Values.manager.webhookPort }} selector: {{- include "capsule.selectorLabels" . | nindent 4 }} sessionAffinity: None diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index e81da44b..ebe7921a 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -32,6 +32,13 @@ manager: # with pods' IP CIDR and admission webhooks are not working hostNetwork: false + # -- Set an alternative to the default container port. + # + # Useful for use in some kubernetes clusters (such as GKE Private) with + # aggregator routing turned on, because pod ports have to be opened manually + # on the firewall side + webhookPort: 9443 + # Additional Capsule Controller Options options: # -- Set the log verbosity of the capsule with a value from 1 to 10 From 47dd56fbaf991260a409764feeae3a9b8172bc39 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 17 Feb 2023 14:42:03 +0100 Subject: [PATCH 104/153] fix: missing proxyservice kinds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Oliver Bähler --- api/v1beta2/owner.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/api/v1beta2/owner.go b/api/v1beta2/owner.go index 7ae29010..c4753c80 100644 --- a/api/v1beta2/owner.go +++ b/api/v1beta2/owner.go @@ -34,7 +34,7 @@ func (p ProxyOperation) String() string { return string(p) } -// +kubebuilder:validation:Enum=Nodes;StorageClasses;IngressClasses;PriorityClasses +// +kubebuilder:validation:Enum=Nodes;StorageClasses;IngressClasses;PriorityClasses;RuntimeClasses;PersistentVolumes type ProxyServiceKind string func (p ProxyServiceKind) String() string { @@ -42,10 +42,12 @@ func (p ProxyServiceKind) String() string { } const ( - NodesProxy ProxyServiceKind = "Nodes" - StorageClassesProxy ProxyServiceKind = "StorageClasses" - IngressClassesProxy ProxyServiceKind = "IngressClasses" - PriorityClassesProxy ProxyServiceKind = "PriorityClasses" + NodesProxy ProxyServiceKind = "Nodes" + StorageClassesProxy ProxyServiceKind = "StorageClasses" + IngressClassesProxy ProxyServiceKind = "IngressClasses" + PriorityClassesProxy ProxyServiceKind = "PriorityClasses" + RuntimeClassesProxy ProxyServiceKind = "RuntimeClasses" + PersistentVolumesProxy ProxyServiceKind = "PersistentVolumes" ListOperation ProxyOperation = "List" UpdateOperation ProxyOperation = "Update" From 7ac0d43b8d7f0ee82c31affd58f25fec525a4b0c Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 17 Feb 2023 14:42:44 +0100 Subject: [PATCH 105/153] chore(kustomize): missing proxyservice kinds --- config/crd/bases/capsule.clastix.io_tenants.yaml | 2 ++ config/install.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index 3ac0cb7b..6f0977c0 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -2840,6 +2840,8 @@ spec: - StorageClasses - IngressClasses - PriorityClasses + - RuntimeClasses + - PersistentVolumes type: string operations: items: diff --git a/config/install.yaml b/config/install.yaml index 7883dd9c..30e8ea64 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -2416,6 +2416,8 @@ spec: - StorageClasses - IngressClasses - PriorityClasses + - RuntimeClasses + - PersistentVolumes type: string operations: items: From 9f184d70e705a089d0b253950b6cd427a30d8ac9 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 17 Feb 2023 14:42:59 +0100 Subject: [PATCH 106/153] chore(helm)!: missing proxyservice kinds --- charts/capsule/crds/tenant-crd.yaml | 1773 +++++++++++++++++++++------ 1 file changed, 1431 insertions(+), 342 deletions(-) diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index 887e8956..d1bbdd10 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -54,17 +54,22 @@ spec: name: Age type: date deprecated: true - deprecationWarning: This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version. + deprecationWarning: This version is going to be dropped in the upcoming version + of Capsule; please, migrate to v1beta2 version. name: v1alpha1 schema: openAPIV3Schema: description: Tenant is the Schema for the tenants API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -79,19 +84,31 @@ spec: subjects: description: kubebuilder:validation:Minimum=1 items: - description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. properties: apiGroup: - description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. type: string kind: - description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. type: string name: description: Name of the object being referenced. type: string namespace: - description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. type: string required: - kind @@ -143,12 +160,15 @@ spec: type: object limitRanges: items: - description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + description: LimitRangeSpec defines a min/max usage limit for resources + that match on kind. properties: limits: - description: Limits is the list of LimitRangeItem objects that are enforced. + description: Limits is the list of LimitRangeItem objects that + are enforced. items: - description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. properties: default: additionalProperties: @@ -157,7 +177,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Default resource requirement limit value by resource name if resource limit is omitted. + description: Default resource requirement limit value + by resource name if resource limit is omitted. type: object defaultRequest: additionalProperties: @@ -166,7 +187,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + description: DefaultRequest is the default resource requirement + request value by resource name if resource request is + omitted. type: object max: additionalProperties: @@ -175,7 +198,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource name. + description: Max usage constraints on this kind by resource + name. type: object maxLimitRequestRatio: additionalProperties: @@ -184,7 +208,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + description: MaxLimitRequestRatio if specified, the named + resource must have a request and limit that are both + non-zero where limit divided by request is less than + or equal to the enumerated value; this represents the + max burst for the named resource. type: object min: additionalProperties: @@ -193,10 +221,12 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource name. + description: Min usage constraints on this kind by resource + name. type: object type: - description: Type of resource that this limit applies to. + description: Type of resource that this limit applies + to. type: string required: - type @@ -226,44 +256,93 @@ spec: description: NetworkPolicySpec provides the specification of a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows the + traffic), OR if the traffic matches at least one egress rule + across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure that + the pods it selects are isolated by default). This field is + beta-level in 1.8 items: - description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by a + NetworkPolicySpec's podSelector. The traffic must match + both ports and to. This type is beta-level in 1.8 properties: ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of destination ports for outgoing traffic. + Each item in this list is combined using a logical OR. + If this field is empty or missing, this rule matches + all ports (traffic not restricted by port). If this + field is present and contains at least one item, then + this rule allows traffic only if the traffic matches + at least one port in the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port to allow + traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range of + ports from port to endPort, inclusive, should + be allowed by the policy. This field cannot be + defined if the port field is not defined or if + the port field is defined as a named (string) + port. The endPort must be equal or greater than + port. This feature is in Beta state and is enabled + by default. It can be disabled using the Feature + Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. This + can either be a numerical or named port on a pod. + If this field is not provided, this matches all + port names and numbers. If present, only traffic + on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) which + traffic must match. If not specified, this field + defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list are + combined using a logical OR operation. If this field + is empty or missing, this rule matches all destinations + (traffic not restricted by destination). If this field + is present and contains at least one item, this rule + allows traffic only if the traffic matches at least + one item in the to list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer to allow + traffic to/from. Only certain combinations of fields + are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither of + the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing the + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs that + should not be included within an IP Block + Valid examples are "192.168.1.1/24" or "2001:db9::/64" + Except values will be rejected if they are + outside the CIDR range items: type: string type: array @@ -271,21 +350,43 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label selector + semantics; if present but empty, it selects all + namespaces. \n If PodSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects all + Pods in the Namespaces selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -297,26 +398,54 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which selects + Pods. This field follows standard label selector + semantics; if present but empty, it selects all + pods. \n If NamespaceSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects the + Pods matching PodSelector in the policy's own + Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -328,7 +457,12 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic @@ -337,23 +471,51 @@ spec: type: object type: array ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + description: List of ingress rules to be applied to the selected + pods. Traffic is allowed to a pod if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows the + traffic), OR if the traffic source is the pod's local node, + OR if the traffic matches at least one ingress rule across + all of the NetworkPolicy objects whose podSelector matches + the pod. If this field is empty then this NetworkPolicy does + not allow any traffic (and serves solely to ensure that the + pods it selects are isolated by default) items: - description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by a + NetworkPolicySpec's podSelector. The traffic must match + both ports and from. properties: from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + description: List of sources which should be able to access + the pods selected for this rule. Items in this list + are combined using a logical OR operation. If this field + is empty or missing, this rule matches all sources (traffic + not restricted by source). If this field is present + and contains at least one item, this rule allows traffic + only if the traffic matches at least one item in the + from list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer to allow + traffic to/from. Only certain combinations of fields + are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither of + the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing the + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs that + should not be included within an IP Block + Valid examples are "192.168.1.1/24" or "2001:db9::/64" + Except values will be rejected if they are + outside the CIDR range items: type: string type: array @@ -361,21 +523,43 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label selector + semantics; if present but empty, it selects all + namespaces. \n If PodSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects all + Pods in the Namespaces selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -387,26 +571,54 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which selects + Pods. This field follows standard label selector + semantics; if present but empty, it selects all + pods. \n If NamespaceSelector is also set, then + the NetworkPolicyPeer as a whole selects the Pods + matching PodSelector in the Namespaces selected + by NamespaceSelector. Otherwise it selects the + Pods matching PodSelector in the policy's own + Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that + the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. items: type: string type: array @@ -418,51 +630,94 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic type: object type: array ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in this + list is combined using a logical OR. If this field is + empty or missing, this rule matches all ports (traffic + not restricted by port). If this field is present and + contains at least one item, then this rule allows traffic + only if the traffic matches at least one port in the + list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port to allow + traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range of + ports from port to endPort, inclusive, should + be allowed by the policy. This field cannot be + defined if the port field is not defined or if + the port field is defined as a named (string) + port. The endPort must be equal or greater than + port. This feature is in Beta state and is enabled + by default. It can be disabled using the Feature + Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. This + can either be a numerical or named port on a pod. + If this field is not provided, this matches all + port names and numbers. If present, only traffic + on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) which + traffic must match. If not specified, this field + defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + description: Selects the pods to which this NetworkPolicy object + applies. The array of ingress rules is applied to any pods + selected by this field. Multiple network policies can select + the same set of pods. In this case, the ingress rules for + each are combined additively. This field is NOT optional and + follows standard label selector semantics. An empty podSelector + matches all pods in this namespace. properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector + applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. This + array is replaced during a strategic merge patch. items: type: string type: array @@ -474,14 +729,31 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress section) + are assumed to affect Ingress. If you want to write an egress-only + policy, you must explicitly specify policyTypes [ "Egress" + ]. Likewise, if you want to write a policy that specifies + that no egress is allowed, you must specify a policyTypes + value that include "Egress" (since such a policy would not + include an Egress section and would otherwise default to just + [ "Ingress" ]). This field is beta-level in 1.8 items: - description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 type: string type: array required: @@ -508,7 +780,8 @@ spec: type: object resourceQuotas: items: - description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + description: ResourceQuotaSpec defines the desired hard limits to + enforce for Quota. properties: hard: additionalProperties: @@ -517,24 +790,39 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + description: 'hard is the set of desired hard limits for each + named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' type: object scopeSelector: - description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + description: scopeSelector is also a collection of filters like + scopes that must match each object tracked by a quota but + expressed using ScopeSelectorOperator in combination with + possible values. For a resource to match, both scopes AND + scopeSelector (if specified in spec), must be matched. properties: matchExpressions: - description: A list of scope selector requirements by scope of the resources. + description: A list of scope selector requirements by scope + of the resources. items: - description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + description: A scoped-resource selector requirement is + a selector that contains values, a scope name, and an + operator that relates the scope name and values. properties: operator: - description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + description: Represents a scope's relationship to + a set of values. Valid operators are In, NotIn, + Exists, DoesNotExist. type: string scopeName: - description: The name of the scope that the selector applies to. + description: The name of the scope that the selector + applies to. type: string values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: An array of string values. If the operator + is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during + a strategic merge patch. items: type: string type: array @@ -546,9 +834,12 @@ spec: type: object x-kubernetes-map-type: atomic scopes: - description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + description: A collection of filters that must match each object + tracked by a quota. If not specified, the quota matches all + objects. items: - description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + description: A ResourceQuotaScope defines a filter that must + match each object tracked by a quota type: string type: array type: object @@ -620,10 +911,14 @@ spec: description: Tenant is the Schema for the tenants API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -631,7 +926,9 @@ spec: description: TenantSpec defines the desired state of Tenant. properties: additionalRoleBindings: - description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. + description: Specifies additional RoleBindings assigned to the Tenant. + Capsule will ensure that all namespaces in the Tenant always contain + the RoleBinding for the given ClusterRole. Optional. items: properties: clusterRoleName: @@ -639,19 +936,31 @@ spec: subjects: description: kubebuilder:validation:Minimum=1 items: - description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. properties: apiGroup: - description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. type: string kind: - description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. type: string name: description: Name of the object being referenced. type: string namespace: - description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. type: string required: - kind @@ -665,7 +974,9 @@ spec: type: object type: array containerRegistries: - description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + description: Specifies the trusted Image Registries assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed trusted registries. Optional. properties: allowed: items: @@ -675,7 +986,9 @@ spec: type: string type: object imagePullPolicies: - description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. + description: Specify the allowed values for the imagePullPolicies + option in Pod resources. Capsule assures that all Pod resources + created in the Tenant can use only one of the allowed policy. Optional. items: enum: - Always @@ -684,10 +997,14 @@ spec: type: string type: array ingressOptions: - description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + description: Specifies options for the Ingress resources, such as + allowed hostnames and IngressClass. Optional. properties: allowedClasses: - description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional. + description: Specifies the allowed IngressClasses assigned to + the Tenant. Capsule assures that all Ingress resources created + in the Tenant can use only one of the allowed IngressClasses. + Optional. properties: allowed: items: @@ -697,7 +1014,10 @@ spec: type: string type: object allowedHostnames: - description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + description: Specifies the allowed hostnames in Ingresses for + the given Tenant. Capsule assures that all Ingress resources + created in the Tenant can use only one of the allowed hostnames. + Optional. properties: allowed: items: @@ -708,7 +1028,15 @@ spec: type: object hostnameCollisionScope: default: Disabled - description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + description: "Defines the scope of hostname collision check performed + when Tenant Owners create Ingress with allowed hostnames. \n + - Cluster: disallow the creation of an Ingress if the pair hostname + and path is already used across the Namespaces managed by Capsule. + \n - Tenant: disallow the creation of an Ingress if the pair + hostname and path is already used across the Namespaces of the + Tenant. \n - Namespace: disallow the creation of an Ingress + if the pair hostname and path is already used in the Ingress + Namespace. \n Optional." enum: - Cluster - Tenant @@ -717,16 +1045,21 @@ spec: type: string type: object limitRanges: - description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + description: Specifies the resource min/max usage restrictions to + the Tenant. The assigned values are inherited by any namespace created + in the Tenant. Optional. properties: items: items: - description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + description: LimitRangeSpec defines a min/max usage limit for + resources that match on kind. properties: limits: - description: Limits is the list of LimitRangeItem objects that are enforced. + description: Limits is the list of LimitRangeItem objects + that are enforced. items: - description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. properties: default: additionalProperties: @@ -735,7 +1068,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Default resource requirement limit value by resource name if resource limit is omitted. + description: Default resource requirement limit value + by resource name if resource limit is omitted. type: object defaultRequest: additionalProperties: @@ -744,7 +1078,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + description: DefaultRequest is the default resource + requirement request value by resource name if resource + request is omitted. type: object max: additionalProperties: @@ -753,7 +1089,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource name. + description: Max usage constraints on this kind by + resource name. type: object maxLimitRequestRatio: additionalProperties: @@ -762,7 +1099,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + description: MaxLimitRequestRatio if specified, the + named resource must have a request and limit that + are both non-zero where limit divided by request + is less than or equal to the enumerated value; this + represents the max burst for the named resource. type: object min: additionalProperties: @@ -771,10 +1112,12 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource name. + description: Min usage constraints on this kind by + resource name. type: object type: - description: Type of resource that this limit applies to. + description: Type of resource that this limit applies + to. type: string required: - type @@ -786,10 +1129,14 @@ spec: type: array type: object namespaceOptions: - description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + description: Specifies options for the Namespaces, such as additional + metadata or maximum number of namespaces allowed for that Tenant. + Once the namespace quota assigned to the Tenant has been reached, + the Tenant owner cannot create further namespaces. Optional. properties: additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + description: Specifies additional labels and annotations the Capsule + operator places on any Namespace resource in the Tenant. Optional. properties: annotations: additionalProperties: @@ -801,57 +1148,116 @@ spec: type: object type: object quota: - description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + description: Specifies the maximum number of namespaces allowed + for that Tenant. Once the namespace quota assigned to the Tenant + has been reached, the Tenant owner cannot create further namespaces. + Optional. format: int32 minimum: 1 type: integer type: object networkPolicies: - description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + description: Specifies the NetworkPolicies assigned to the Tenant. + The assigned NetworkPolicies are inherited by any namespace created + in the Tenant. Optional. properties: items: items: - description: NetworkPolicySpec provides the specification of a NetworkPolicy + description: NetworkPolicySpec provides the specification of + a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows + the traffic), OR if the traffic matches at least one egress + rule across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure + that the pods it selects are isolated by default). This + field is beta-level in 1.8 items: - description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and to. This type is beta-level in + 1.8 properties: ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of destination ports for outgoing + traffic. Each item in this list is combined using + a logical OR. If this field is empty or missing, + this rule matches all ports (traffic not restricted + by port). If this field is present and contains + at least one item, then this rule allows traffic + only if the traffic matches at least one port in + the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port + to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list + are combined using a logical OR operation. If this + field is empty or missing, this rule matches all + destinations (traffic not restricted by destination). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the to list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range items: type: string type: array @@ -859,21 +1265,45 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -885,26 +1315,55 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -916,7 +1375,12 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic @@ -925,23 +1389,53 @@ spec: type: object type: array ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + description: List of ingress rules to be applied to the + selected pods. Traffic is allowed to a pod if there are + no NetworkPolicies selecting the pod (and cluster policy + otherwise allows the traffic), OR if the traffic source + is the pod's local node, OR if the traffic matches at + least one ingress rule across all of the NetworkPolicy + objects whose podSelector matches the pod. If this field + is empty then this NetworkPolicy does not allow any traffic + (and serves solely to ensure that the pods it selects + are isolated by default) items: - description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and from. properties: from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + description: List of sources which should be able + to access the pods selected for this rule. Items + in this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all sources (traffic not restricted by source). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the from list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range items: type: string type: array @@ -949,21 +1443,45 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -975,26 +1493,55 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -1006,51 +1553,96 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic type: object type: array ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in + this list is combined using a logical OR. If this + field is empty or missing, this rule matches all + ports (traffic not restricted by port). If this + field is present and contains at least one item, + then this rule allows traffic only if the traffic + matches at least one port in the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port + to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + description: Selects the pods to which this NetworkPolicy + object applies. The array of ingress rules is applied + to any pods selected by this field. Multiple network policies + can select the same set of pods. In this case, the ingress + rules for each are combined additively. This field is + NOT optional and follows standard label selector semantics. + An empty podSelector matches all pods in this namespace. properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector + applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. items: type: string type: array @@ -1062,14 +1654,32 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress + section) are assumed to affect Ingress. If you want to + write an egress-only policy, you must explicitly specify + policyTypes [ "Egress" ]. Likewise, if you want to write + a policy that specifies that no egress is allowed, you + must specify a policyTypes value that include "Egress" + (since such a policy would not include an Egress section + and would otherwise default to just [ "Ingress" ]). This + field is beta-level in 1.8 items: - description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 type: string type: array required: @@ -1080,14 +1690,19 @@ spec: nodeSelector: additionalProperties: type: string - description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + description: Specifies the label to control the placement of pods + on a given pool of worker nodes. All namespaces created within the + Tenant will have the node selector annotation. This annotation tells + the Kubernetes scheduler to place pods on the nodes having the selector + label. Optional. type: object owners: description: Specifies the owners of the Tenant. Mandatory. items: properties: kind: - description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + description: Kind of tenant owner. Possible values are "User", + "Group", and "ServiceAccount" enum: - User - Group @@ -1126,7 +1741,9 @@ spec: type: object type: array priorityClasses: - description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. Optional. + description: Specifies the allowed priorityClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed PriorityClasses. Optional. properties: allowed: items: @@ -1136,11 +1753,17 @@ spec: type: string type: object resourceQuotas: - description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + description: Specifies a list of ResourceQuota resources assigned + to the Tenant. The assigned values are inherited by any namespace + created in the Tenant. The Capsule operator aggregates ResourceQuota + at Tenant level, so that the hard quota is never crossed for the + given Tenant. This permits the Tenant owner to consume resources + in the Tenant regardless of the namespace. Optional. properties: items: items: - description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + description: ResourceQuotaSpec defines the desired hard limits + to enforce for Quota. properties: hard: additionalProperties: @@ -1149,24 +1772,40 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + description: 'hard is the set of desired hard limits for + each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' type: object scopeSelector: - description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + description: scopeSelector is also a collection of filters + like scopes that must match each object tracked by a quota + but expressed using ScopeSelectorOperator in combination + with possible values. For a resource to match, both scopes + AND scopeSelector (if specified in spec), must be matched. properties: matchExpressions: - description: A list of scope selector requirements by scope of the resources. + description: A list of scope selector requirements by + scope of the resources. items: - description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + description: A scoped-resource selector requirement + is a selector that contains values, a scope name, + and an operator that relates the scope name and + values. properties: operator: - description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + description: Represents a scope's relationship + to a set of values. Valid operators are In, + NotIn, Exists, DoesNotExist. type: string scopeName: - description: The name of the scope that the selector applies to. + description: The name of the scope that the selector + applies to. type: string values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is + replaced during a strategic merge patch. items: type: string type: array @@ -1178,26 +1817,33 @@ spec: type: object x-kubernetes-map-type: atomic scopes: - description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + description: A collection of filters that must match each + object tracked by a quota. If not specified, the quota + matches all objects. items: - description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + description: A ResourceQuotaScope defines a filter that + must match each object tracked by a quota type: string type: array type: object type: array scope: default: Tenant - description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + description: Define if the Resource Budget should compute resource + across all Namespaces in the Tenant or individually per cluster. + Default is Tenant enum: - Tenant - Namespace type: string type: object serviceOptions: - description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + description: Specifies options for the Service, such as additional + metadata or block of certain type of Services. Optional. properties: additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + description: Specifies additional labels and annotations the Capsule + operator places on any Service resource in the Tenant. Optional. properties: annotations: additionalProperties: @@ -1213,19 +1859,24 @@ spec: properties: externalName: default: true - description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if ExternalName service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean loadBalancer: default: true - description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if LoadBalancer service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean nodePort: default: true - description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if NodePort service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean type: object externalIPs: - description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + description: Specifies the external IPs that can be used in Services + with type ClusterIP. An empty list means no IPs are allowed. + Optional. properties: allowed: items: @@ -1237,7 +1888,10 @@ spec: type: object type: object storageClasses: - description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. + description: Specifies the allowed StorageClasses assigned to the + Tenant. Capsule assures that all PersistentVolumeClaim resources + created in the Tenant can use only one of the allowed StorageClasses. + Optional. properties: allowed: items: @@ -1262,7 +1916,8 @@ spec: type: integer state: default: Active - description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + description: The operational state of the Tenant. Possible values + are "Active", "Cordoned". enum: - Cordoned - Active @@ -1303,10 +1958,14 @@ spec: description: Tenant is the Schema for the tenants API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -1314,7 +1973,9 @@ spec: description: TenantSpec defines the desired state of Tenant. properties: additionalRoleBindings: - description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. + description: Specifies additional RoleBindings assigned to the Tenant. + Capsule will ensure that all namespaces in the Tenant always contain + the RoleBinding for the given ClusterRole. Optional. items: properties: clusterRoleName: @@ -1322,19 +1983,31 @@ spec: subjects: description: kubebuilder:validation:Minimum=1 items: - description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. + description: Subject contains a reference to the object or + user identities a role binding applies to. This can either + hold a direct API object reference, or a value for non-objects + such as user and group names. properties: apiGroup: - description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + description: APIGroup holds the API group of the referenced + subject. Defaults to "" for ServiceAccount subjects. + Defaults to "rbac.authorization.k8s.io" for User and + Group subjects. type: string kind: - description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. + description: Kind of object being referenced. Values defined + by this API group are "User", "Group", and "ServiceAccount". + If the Authorizer does not recognized the kind value, + the Authorizer should report an error. type: string name: description: Name of the object being referenced. type: string namespace: - description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. + description: Namespace of the referenced object. If the + object kind is non-namespace, such as "User" or "Group", + and this value is not empty the Authorizer should report + an error. type: string required: - kind @@ -1348,7 +2021,9 @@ spec: type: object type: array containerRegistries: - description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. + description: Specifies the trusted Image Registries assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed trusted registries. Optional. properties: allowed: items: @@ -1358,10 +2033,13 @@ spec: type: string type: object cordoned: - description: Toggling the Tenant resources cordoning, when enable resources cannot be deleted. + description: Toggling the Tenant resources cordoning, when enable + resources cannot be deleted. type: boolean imagePullPolicies: - description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. + description: Specify the allowed values for the imagePullPolicies + option in Pod resources. Capsule assures that all Pod resources + created in the Tenant can use only one of the allowed policy. Optional. items: enum: - Always @@ -1370,13 +2048,19 @@ spec: type: string type: array ingressOptions: - description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. + description: Specifies options for the Ingress resources, such as + allowed hostnames and IngressClass. Optional. properties: allowWildcardHostnames: - description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. + description: Toggles the ability for Ingress resources created + in a Tenant to have a hostname wildcard. type: boolean allowedClasses: - description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. A default value can be specified, and all the Ingress resources created will inherit the declared class. Optional. + description: Specifies the allowed IngressClasses assigned to + the Tenant. Capsule assures that all Ingress resources created + in the Tenant can use only one of the allowed IngressClasses. + A default value can be specified, and all the Ingress resources + created will inherit the declared class. Optional. properties: allowed: items: @@ -1387,18 +2071,28 @@ spec: default: type: string matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector + applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. items: type: string type: array @@ -1410,12 +2104,19 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic allowedHostnames: - description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + description: Specifies the allowed hostnames in Ingresses for + the given Tenant. Capsule assures that all Ingress resources + created in the Tenant can use only one of the allowed hostnames. + Optional. properties: allowed: items: @@ -1426,7 +2127,15 @@ spec: type: object hostnameCollisionScope: default: Disabled - description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + description: "Defines the scope of hostname collision check performed + when Tenant Owners create Ingress with allowed hostnames. \n + - Cluster: disallow the creation of an Ingress if the pair hostname + and path is already used across the Namespaces managed by Capsule. + \n - Tenant: disallow the creation of an Ingress if the pair + hostname and path is already used across the Namespaces of the + Tenant. \n - Namespace: disallow the creation of an Ingress + if the pair hostname and path is already used in the Ingress + Namespace. \n Optional." enum: - Cluster - Tenant @@ -1435,16 +2144,21 @@ spec: type: string type: object limitRanges: - description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + description: Specifies the resource min/max usage restrictions to + the Tenant. The assigned values are inherited by any namespace created + in the Tenant. Optional. properties: items: items: - description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + description: LimitRangeSpec defines a min/max usage limit for + resources that match on kind. properties: limits: - description: Limits is the list of LimitRangeItem objects that are enforced. + description: Limits is the list of LimitRangeItem objects + that are enforced. items: - description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + description: LimitRangeItem defines a min/max usage limit + for any resource that matches on kind. properties: default: additionalProperties: @@ -1453,7 +2167,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Default resource requirement limit value by resource name if resource limit is omitted. + description: Default resource requirement limit value + by resource name if resource limit is omitted. type: object defaultRequest: additionalProperties: @@ -1462,7 +2177,9 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + description: DefaultRequest is the default resource + requirement request value by resource name if resource + request is omitted. type: object max: additionalProperties: @@ -1471,7 +2188,8 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource name. + description: Max usage constraints on this kind by + resource name. type: object maxLimitRequestRatio: additionalProperties: @@ -1480,7 +2198,11 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + description: MaxLimitRequestRatio if specified, the + named resource must have a request and limit that + are both non-zero where limit divided by request + is less than or equal to the enumerated value; this + represents the max burst for the named resource. type: object min: additionalProperties: @@ -1489,10 +2211,12 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource name. + description: Min usage constraints on this kind by + resource name. type: object type: - description: Type of resource that this limit applies to. + description: Type of resource that this limit applies + to. type: string required: - type @@ -1504,10 +2228,14 @@ spec: type: array type: object namespaceOptions: - description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + description: Specifies options for the Namespaces, such as additional + metadata or maximum number of namespaces allowed for that Tenant. + Once the namespace quota assigned to the Tenant has been reached, + the Tenant owner cannot create further namespaces. Optional. properties: additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + description: Specifies additional labels and annotations the Capsule + operator places on any Namespace resource in the Tenant. Optional. properties: annotations: additionalProperties: @@ -1519,7 +2247,8 @@ spec: type: object type: object forbiddenAnnotations: - description: Define the annotations that a Tenant Owner cannot set for their Namespace resources. + description: Define the annotations that a Tenant Owner cannot + set for their Namespace resources. properties: denied: items: @@ -1529,7 +2258,8 @@ spec: type: string type: object forbiddenLabels: - description: Define the labels that a Tenant Owner cannot set for their Namespace resources. + description: Define the labels that a Tenant Owner cannot set + for their Namespace resources. properties: denied: items: @@ -1539,57 +2269,116 @@ spec: type: string type: object quota: - description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + description: Specifies the maximum number of namespaces allowed + for that Tenant. Once the namespace quota assigned to the Tenant + has been reached, the Tenant owner cannot create further namespaces. + Optional. format: int32 minimum: 1 type: integer type: object networkPolicies: - description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + description: Specifies the NetworkPolicies assigned to the Tenant. + The assigned NetworkPolicies are inherited by any namespace created + in the Tenant. Optional. properties: items: items: - description: NetworkPolicySpec provides the specification of a NetworkPolicy + description: NetworkPolicySpec provides the specification of + a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + description: List of egress rules to be applied to the selected + pods. Outgoing traffic is allowed if there are no NetworkPolicies + selecting the pod (and cluster policy otherwise allows + the traffic), OR if the traffic matches at least one egress + rule across all of the NetworkPolicy objects whose podSelector + matches the pod. If this field is empty then this NetworkPolicy + limits all outgoing traffic (and serves solely to ensure + that the pods it selects are isolated by default). This + field is beta-level in 1.8 items: - description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + description: NetworkPolicyEgressRule describes a particular + set of traffic that is allowed out of pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and to. This type is beta-level in + 1.8 properties: ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of destination ports for outgoing + traffic. Each item in this list is combined using + a logical OR. If this field is empty or missing, + this rule matches all ports (traffic not restricted + by port). If this field is present and contains + at least one item, then this rule allows traffic + only if the traffic matches at least one port in + the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port + to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + description: List of destinations for outgoing traffic + of pods selected for this rule. Items in this list + are combined using a logical OR operation. If this + field is empty or missing, this rule matches all + destinations (traffic not restricted by destination). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the to list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range items: type: string type: array @@ -1597,21 +2386,45 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -1623,26 +2436,55 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -1654,7 +2496,12 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic @@ -1663,23 +2510,53 @@ spec: type: object type: array ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + description: List of ingress rules to be applied to the + selected pods. Traffic is allowed to a pod if there are + no NetworkPolicies selecting the pod (and cluster policy + otherwise allows the traffic), OR if the traffic source + is the pod's local node, OR if the traffic matches at + least one ingress rule across all of the NetworkPolicy + objects whose podSelector matches the pod. If this field + is empty then this NetworkPolicy does not allow any traffic + (and serves solely to ensure that the pods it selects + are isolated by default) items: - description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + description: NetworkPolicyIngressRule describes a particular + set of traffic that is allowed to the pods matched by + a NetworkPolicySpec's podSelector. The traffic must + match both ports and from. properties: from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + description: List of sources which should be able + to access the pods selected for this rule. Items + in this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all sources (traffic not restricted by source). + If this field is present and contains at least one + item, this rule allows traffic only if the traffic + matches at least one item in the from list. items: - description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + description: NetworkPolicyPeer describes a peer + to allow traffic to/from. Only certain combinations + of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: IPBlock defines policy on a particular + IPBlock. If this field is set then neither + of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: CIDR is a string representing + the IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: Except is a slice of CIDRs + that should not be included within an + IP Block Valid examples are "192.168.1.1/24" + or "2001:db9::/64" Except values will + be rejected if they are outside the CIDR + range items: type: string type: array @@ -1687,21 +2564,45 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "Selects Namespaces using cluster-scoped + labels. This field follows standard label + selector semantics; if present but empty, + it selects all namespaces. \n If PodSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects all Pods in the Namespaces + selected by NamespaceSelector." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -1713,26 +2614,55 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "This is a label selector which + selects Pods. This field follows standard + label selector semantics; if present but empty, + it selects all pods. \n If NamespaceSelector + is also set, then the NetworkPolicyPeer as + a whole selects the Pods matching PodSelector + in the Namespaces selected by NamespaceSelector. + Otherwise it selects the Pods matching PodSelector + in the policy's own Namespace." properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key + that the selector applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. items: type: string type: array @@ -1744,51 +2674,96 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic type: object type: array ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: List of ports which should be made accessible + on the pods selected for this rule. Each item in + this list is combined using a logical OR. If this + field is empty or missing, this rule matches all + ports (traffic not restricted by port). If this + field is present and contains at least one item, + then this rule allows traffic only if the traffic + matches at least one port in the list. items: - description: NetworkPolicyPort describes a port to allow traffic on + description: NetworkPolicyPort describes a port + to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: If set, indicates that the range + of ports from port to endPort, inclusive, + should be allowed by the policy. This field + cannot be defined if the port field is not + defined or if the port field is defined as + a named (string) port. The endPort must be + equal or greater than port. This feature is + in Beta state and is enabled by default. It + can be disabled using the Feature Gate "NetworkPolicyEndPort". format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: The port on the given protocol. + This can either be a numerical or named port + on a pod. If this field is not provided, this + matches all port names and numbers. If present, + only traffic on the specified protocol AND + port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: The protocol (TCP, UDP, or SCTP) + which traffic must match. If not specified, + this field defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + description: Selects the pods to which this NetworkPolicy + object applies. The array of ingress rules is applied + to any pods selected by this field. Multiple network policies + can select the same set of pods. In this case, the ingress + rules for each are combined additively. This field is + NOT optional and follows standard label selector semantics. + An empty podSelector matches all pods in this namespace. properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector + that contains values, a key, and an operator that + relates the key and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector + applies to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship + to a set of values. Valid operators are In, + NotIn, Exists and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists + or DoesNotExist, the values array must be empty. + This array is replaced during a strategic merge + patch. items: type: string type: array @@ -1800,14 +2775,32 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field + is "key", the operator is "In", and the values array + contains only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + description: List of rule types that the NetworkPolicy relates + to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", + "Egress"]. If this field is not specified, it will default + based on the existence of Ingress or Egress rules; policies + that contain an Egress section are assumed to affect Egress, + and all policies (whether or not they contain an Ingress + section) are assumed to affect Ingress. If you want to + write an egress-only policy, you must explicitly specify + policyTypes [ "Egress" ]. Likewise, if you want to write + a policy that specifies that no egress is allowed, you + must specify a policyTypes value that include "Egress" + (since such a policy would not include an Egress section + and would otherwise default to just [ "Ingress" ]). This + field is beta-level in 1.8 items: - description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + description: PolicyType string describes the NetworkPolicy + type This type is beta-level in 1.8 type: string type: array required: @@ -1818,7 +2811,11 @@ spec: nodeSelector: additionalProperties: type: string - description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + description: Specifies the label to control the placement of pods + on a given pool of worker nodes. All namespaces created within the + Tenant will have the node selector annotation. This annotation tells + the Kubernetes scheduler to place pods on the nodes having the selector + label. Optional. type: object owners: description: Specifies the owners of the Tenant. Mandatory. @@ -1828,12 +2825,14 @@ spec: default: - admin - capsule-namespace-deleter - description: Defines additional cluster-roles for the specific Owner. + description: Defines additional cluster-roles for the specific + Owner. items: type: string type: array kind: - description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + description: Kind of tenant owner. Possible values are "User", + "Group", and "ServiceAccount" enum: - User - Group @@ -1852,6 +2851,8 @@ spec: - StorageClasses - IngressClasses - PriorityClasses + - RuntimeClasses + - PersistentVolumes type: string operations: items: @@ -1872,10 +2873,15 @@ spec: type: object type: array preventDeletion: - description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined. + description: Prevent accidental deletion of the Tenant. When enabled, + the deletion request will be declined. type: boolean priorityClasses: - description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. A default value can be specified, and all the Pod resources created will inherit the declared class. Optional. + description: Specifies the allowed priorityClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed PriorityClasses. A default value + can be specified, and all the Pod resources created will inherit + the declared class. Optional. properties: allowed: items: @@ -1886,18 +2892,28 @@ spec: default: type: string matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector applies + to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -1909,16 +2925,26 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. type: object type: object x-kubernetes-map-type: atomic resourceQuotas: - description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + description: Specifies a list of ResourceQuota resources assigned + to the Tenant. The assigned values are inherited by any namespace + created in the Tenant. The Capsule operator aggregates ResourceQuota + at Tenant level, so that the hard quota is never crossed for the + given Tenant. This permits the Tenant owner to consume resources + in the Tenant regardless of the namespace. Optional. properties: items: items: - description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + description: ResourceQuotaSpec defines the desired hard limits + to enforce for Quota. properties: hard: additionalProperties: @@ -1927,24 +2953,40 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + description: 'hard is the set of desired hard limits for + each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' type: object scopeSelector: - description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + description: scopeSelector is also a collection of filters + like scopes that must match each object tracked by a quota + but expressed using ScopeSelectorOperator in combination + with possible values. For a resource to match, both scopes + AND scopeSelector (if specified in spec), must be matched. properties: matchExpressions: - description: A list of scope selector requirements by scope of the resources. + description: A list of scope selector requirements by + scope of the resources. items: - description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + description: A scoped-resource selector requirement + is a selector that contains values, a scope name, + and an operator that relates the scope name and + values. properties: operator: - description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + description: Represents a scope's relationship + to a set of values. Valid operators are In, + NotIn, Exists, DoesNotExist. type: string scopeName: - description: The name of the scope that the selector applies to. + description: The name of the scope that the selector + applies to. type: string values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: An array of string values. If the + operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is + replaced during a strategic merge patch. items: type: string type: array @@ -1956,23 +2998,30 @@ spec: type: object x-kubernetes-map-type: atomic scopes: - description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + description: A collection of filters that must match each + object tracked by a quota. If not specified, the quota + matches all objects. items: - description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + description: A ResourceQuotaScope defines a filter that + must match each object tracked by a quota type: string type: array type: object type: array scope: default: Tenant - description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + description: Define if the Resource Budget should compute resource + across all Namespaces in the Tenant or individually per cluster. + Default is Tenant enum: - Tenant - Namespace type: string type: object runtimeClasses: - description: Specifies the allowed RuntimeClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. Optional. + description: Specifies the allowed RuntimeClasses assigned to the + Tenant. Capsule assures that all Pods resources created in the Tenant + can use only one of the allowed RuntimeClasses. Optional. properties: allowed: items: @@ -1981,18 +3030,28 @@ spec: allowedRegex: type: string matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector applies + to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -2004,15 +3063,21 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. type: object type: object x-kubernetes-map-type: atomic serviceOptions: - description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + description: Specifies options for the Service, such as additional + metadata or block of certain type of Services. Optional. properties: additionalMetadata: - description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + description: Specifies additional labels and annotations the Capsule + operator places on any Service resource in the Tenant. Optional. properties: annotations: additionalProperties: @@ -2028,19 +3093,24 @@ spec: properties: externalName: default: true - description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if ExternalName service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean loadBalancer: default: true - description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if LoadBalancer service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean nodePort: default: true - description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + description: Specifies if NodePort service type resources + are allowed for the Tenant. Default is true. Optional. type: boolean type: object externalIPs: - description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + description: Specifies the external IPs that can be used in Services + with type ClusterIP. An empty list means no IPs are allowed. + Optional. properties: allowed: items: @@ -2052,7 +3122,11 @@ spec: type: object type: object storageClasses: - description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class. Optional. + description: Specifies the allowed StorageClasses assigned to the + Tenant. Capsule assures that all PersistentVolumeClaim resources + created in the Tenant can use only one of the allowed StorageClasses. + A default value can be specified, and all the PersistentVolumeClaim + resources created will inherit the declared class. Optional. properties: allowed: items: @@ -2063,18 +3137,28 @@ spec: default: type: string matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector applies + to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -2086,7 +3170,11 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. type: object type: object x-kubernetes-map-type: atomic @@ -2106,7 +3194,8 @@ spec: type: integer state: default: Active - description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + description: The operational state of the Tenant. Possible values + are "Active", "Cordoned". enum: - Cordoned - Active From e0f47bc3ecfcf5347383c571782758fcda7ca719 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 17 Feb 2023 14:43:07 +0100 Subject: [PATCH 107/153] docs: missing proxyservice kinds --- docs/content/general/crds-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/general/crds-apis.md b/docs/content/general/crds-apis.md index d30c4234..395e7060 100644 --- a/docs/content/general/crds-apis.md +++ b/docs/content/general/crds-apis.md @@ -3081,7 +3081,7 @@ TenantSpec defines the desired state of Tenant.

From d791fdb996a7ebea42b0cf052de7f42e852c462e Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 4 Mar 2023 18:08:34 +0100 Subject: [PATCH 108/153] docs: latest changes in capsule proxy --- docs/content/general/proxy.md | 182 +++++++++++++++++++++++++++++----- 1 file changed, 156 insertions(+), 26 deletions(-) diff --git a/docs/content/general/proxy.md b/docs/content/general/proxy.md index 19cbed5d..0ff70939 100644 --- a/docs/content/general/proxy.md +++ b/docs/content/general/proxy.md @@ -10,7 +10,7 @@ Error from server (Forbidden): namespaces is forbidden: User "alice" cannot list resource "namespaces" in API group "" at the cluster scope ``` -However, the user can have permissions on some namespaces +However, the user can have permission on some namespaces ``` $ kubectl auth can-i [get|list|watch|delete] ns oil-production @@ -21,13 +21,13 @@ The reason, as the error message reported, is that the RBAC _list_ action is ava To overcome this problem, many Kubernetes distributions introduced mirrored custom resources supported by a custom set of ACL-filtered APIs. However, this leads to radically change the user's experience of Kubernetes by introducing hard customizations that make it painful to move from one distribution to another. -With **Capsule**, we took a different approach. As one of the key goals, we want to keep the same user's experience on all the distributions of Kubernetes. We want people to use the standard tools they already know and love and it should just work. +With **Capsule**, we took a different approach. As one of the key goals, we want to keep the same user experience on all the distributions of Kubernetes. We want people to use the standard tools they already know and love and it should just work. ## How it works The `capsule-proxy` implements a simple reverse proxy that intercepts only specific requests to the APIs server and Capsule does all the magic behind the scenes. -Current implementation filters the following requests: +The current implementation filters the following requests: * `/api/scheduling.k8s.io/{v1}/priorityclasses{/name}` * `/api/v1/namespaces{/name}` @@ -37,13 +37,16 @@ Current implementation filters the following requests: * `/apis/metrics.k8s.io/{v1beta1}/nodes{/name}` * `/apis/networking.k8s.io/{v1,v1beta1}/ingressclasses{/name}` * `/apis/storage.k8s.io/v1/storageclasses{/name}` +* `/apis/node.k8s.io/v1/runtimeclasses{/name}` +* `/api/v1/persistentvolumes{/name}` -All other requests are proxied transparently to the APIs server, so no side effects are expected. We're planning to add new APIs in the future, so [PRs are welcome](https://github.com/clastix/capsule-proxy)! +All other requests are proxy-passed transparently to the API server, so no side effects are expected. +We're planning to add new APIs in the future, so [PRs are welcome](https://github.com/clastix/capsule-proxy)! ## Installation Capsule Proxy is an optional add-on of the main Capsule Operator, so make sure you have a working instance of Capsule before attempting to install it. -Use the `capsule-proxy` only if you want Tenant Owners to list their own Cluster-Scope resources. +Use the `capsule-proxy` only if you want Tenant Owners to list their Cluster-Scope resources. The `capsule-proxy` can be deployed in standalone mode, e.g. running as a pod bridging any Kubernetes client to the APIs server. Optionally, it can be deployed as a sidecar container in the backend of a dashboard. @@ -72,22 +75,22 @@ Here how it looks like when exposed through an Ingress Controller: ## CLI flags - `capsule-configuration-name`: name of the `CapsuleConfiguration` resource which is containing the [Capsule configurations](/docs/general/references/#capsule-configuration) (default: `default`) -- `capsule-user-group` (deprecated): old way to specify the user groups which request must be intercepted by the proxy -- `ignored-user-group`: names of the groups which requests must be ignored and proxy-passed to the upstream server +- `capsule-user-group` (deprecated): the old way to specify the user groups whose request must be intercepted by the proxy +- `ignored-user-group`: names of the groups whose requests must be ignored and proxy-passed to the upstream server - `listening-port`: HTTP port the proxy listens to (default: `9001`) - `oidc-username-claim`: the OIDC field name used to identify the user (default: `preferred_username`), the proper value can be extracted from the Kubernetes API Server flags -- `enable-ssl`: enable the bind on HTTPS for secure communication, allowing client-based certificate, also knows as mutual TLS (default: `true`) +- `enable-ssl`: enable the bind on HTTPS for secure communication, allowing client-based certificate, also known as mutual TLS (default: `true`) - `ssl-cert-path`: path to the TLS certificate, then TLS mode is enabled (default: `/opt/capsule-proxy/tls.crt`) - `ssl-key-path`: path to the TLS certificate key, when TLS mode is enabled (default: `/opt/capsule-proxy/tls.key`) - `rolebindings-resync-period`: resync period for RoleBinding resources reflector, lower values can help if you're facing [flaky etcd connection](https://github.com/clastix/capsule-proxy/issues/174) (default: `10h`) ## User Authentication -The `capsule-proxy` intercepts all the requests from the `kubectl` client directed to the APIs Server. Users using a TLS client based authentication with certificate and key are able to talks with APIs Server since it is able to forward client certificates to the Kubernetes APIs server. +The `capsule-proxy` intercepts all the requests from the `kubectl` client directed to the APIs Server. Users using a TLS client-based authentication with a certificate and key can talk with the API Server since it can forward client certificates to the Kubernetes APIs server. -It is possible to protect the `capsule-proxy` using a certificate provided by Let's Encrypt. Keep in mind that, in this way, the TLS termination will be executed by the Ingress Controller, meaning that the authentication based on client certificate will be withdrawn and not reversed to the upstream. +It is possible to protect the `capsule-proxy` using a certificate provided by Let's Encrypt. Keep in mind that, in this way, the TLS termination will be executed by the Ingress Controller, meaning that the authentication based on the client certificate will be withdrawn and not reversed to the upstream. -If your prerequisite is exposing `capsule-proxy` using an Ingress, you must rely on the token-based authentication, for example OIDC or Bearer tokens. Users providing tokens are always able to reach the APIs Server. +If your prerequisite is exposing `capsule-proxy` using an Ingress, you must rely on the token-based authentication, for example, OIDC or Bearer tokens. Users providing tokens are always able to reach the APIs Server. ## Kubernetes dashboards integration @@ -124,6 +127,8 @@ The proxy setting `kind` is an __enum__ accepting the supported resources: - `StorageClasses` - `IngressClasses` - `PriorityClasses` +- `RuntimeClasses` +- `PersistentVolumes` Each Resource kind can be granted with several verbs, such as: @@ -131,6 +136,14 @@ Each Resource kind can be granted with several verbs, such as: - `Update` - `Delete` +## Cluster-scoped resources selection strategy precedence + +Starting from [Capsule v0.2.0](https://github.com/clastix/capsule/releases/tag/v0.2.0), selection of cluster-scoped resources based on labels has been introduced. + +Due to the limitations of Kubernetes API Server which not support `OR` label selector, the Capsule core team decided to give precedence to the label selector over the exact and regex match. + +Capsule is going to deprecate in the upcoming feature the selection based on exact names and regex in order to approach entirely to the matching labels approach of Kubernetes itself. + ### Namespaces As tenant owner `alice`, you can use `kubectl` to create some namespaces: @@ -162,7 +175,7 @@ metadata: EOF namespace/solar-development unchanged -# or, in case of non existing Namespace: +# or, in case of non-existing Namespace: namespace/solar-development created ``` @@ -202,7 +215,7 @@ When issuing a `kubectl describe node`, some other endpoints are put in place: * `api/v1/pods?fieldSelector=spec.nodeName%3D{name}` * `/apis/coordination.k8s.io/v1/namespaces/kube-node-lease/leases/{name}` -These are mandatory in order to retrieve the list of the running Pods on the required node, and providing info about the lease status of it. +These are mandatory to retrieve the list of the running Pods on the required node and provide info about its lease status. ### Storage Classes @@ -239,7 +252,7 @@ glusterfs rook.io/glusterfs Delete WaitForFirstConsum zol zfs-on-linux/zfs Delete WaitForFirstConsumer false 54m ``` -The expected output using `capsule-proxy` is the retrieval of the `custom` Storage Class as well the other ones matching the regex `\w+fs`. +The expected output using `capsule-proxy` is the retrieval of the `custom` Storage Class as well as the other ones matching the regex `\w+fs`. ```bash $ kubectl --context alice-oidc@mycluster get storageclasses @@ -259,7 +272,6 @@ metadata: name: cephfs name: cephfs provisioner: cephfs - ``` ### Ingress Classes @@ -286,7 +298,7 @@ spec: allowedRegex: "\\w+-lb" ``` -In the Kubernetes cluster we could have more Ingress Class resources, some of them forbidden and non-usable by the Tenant owner. +In the Kubernetes cluster, we could have more Ingress Class resources, some of them forbidden and non-usable by the Tenant owner. ```bash $ kubectl --context admin@mycluster get ingressclasses @@ -385,23 +397,141 @@ globalDefault: false description: "Priority class for Tenants" ``` +### Runtime Classes + +Allowed RuntimeClasses assigned to a Tenant Owner can be enforced as follows: + +```yaml +apiVersion: capsule.clastix.io/v1beta2 +kind: Tenant +metadata: + name: oil +spec: + owners: + - kind: User + name: alice + proxySettings: + - kind: PriorityClasses + operations: + - List + runtimeClasses: + matchExpressions: + - key: capsule.clastix.io/qos + operator: Exists + values: + - bronze + - silver +``` + +In the Kubernetes cluster we could have more RuntimeClasses resources, some of them forbidden and non-usable by the Tenant owner. + +```bash +$ kubectl --context admin@mycluster get runtimeclasses.node.k8s.io --show-labels +NAME HANDLER AGE LABELS +bronze bronze 21h capsule.clastix.io/qos=bronze +default myconfiguration 21h +gold gold 21h capsule.clastix.io/qos=gold +silver silver 21h capsule.clastix.io/qos=silver +``` + +The expected output using `capsule-proxy` is the retrieval of the `bronze` and `silver` ones. + +```bash +$ kubectl --context alice-oidc@mycluster get runtimeclasses.node.k8s.io +NAME HANDLER AGE +bronze bronze 21h +silver silver 21h +``` + +> `RuntimeClass` is one of the latest implementations in Capsule Proxy and is adhering to the new selection strategy based on labels selector, rather than exact match and regex ones. +> +> The latter ones are going to be deprecated in the upcoming releases of Capsule. + +### Persistent Volumes + +A Tenant can request persistent volumes through the `PersistentVolumeClaim` API, and get a volume from it. + +Starting from release v0.2.0, all the `PersistentVolumes` are labelled with the Capsule label that is used by the Capsule Proxy to allow the retrieval. + +```yaml +apiVersion: v1 +kind: PersistentVolume +metadata: + annotations: + finalizers: + - kubernetes.io/pv-protection + labels: + capsule.clastix.io/tenant: oil + name: data-01 +spec: + accessModes: + - ReadWriteOnce + capacity: + storage: 10Gi + hostPath: + path: /mnt/data + type: "" + persistentVolumeReclaimPolicy: Retain + storageClassName: manual + volumeMode: Filesystem +``` + +> Please, notice the label `capsule.clastix.io/tenant` matching the Tenant name. + +With that said, a multi-tenant cluster can be made of several volumes, each one for different tenants. + +```bash +$ kubectl --context admin@mycluster get persistentvolumes --show-labels +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE LABELS +data-01 10Gi RWO Retain Available manual 17h capsule.clastix.io/tenant=oil +data-02 10Gi RWO Retain Available manual 17h capsule.clastix.io/tenant=gas + +``` + +For the `oil` Tenant, Alice has the required permission to list Volumes. + +```yaml +apiVersion: capsule.clastix.io/v1beta2 +kind: Tenant +metadata: + name: oil +spec: + owners: + - kind: User + name: alice + proxySettings: + - kind: PersistentVolumes + operations: + - List +``` + +The expected output using `capsule-proxy` is the retrieval of the PVs used currently, or in the past, by the PVCs in their Tenants. + +```bash +$ kubectl --context alice-oidc@mycluster get persistentvolumes +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE +data-01 10Gi RWO Retain Available manual 17h +``` + ### ProxySetting Use Case -Consider a scenario, where a cluster admin creates a tenant and assign ownership of the tenant to a user, so called tenant owner. Afterwards, tenant owner would in turn like to provide access to their cluster-scoped resources to a set of users (e.g. non-owners or tenant users), groups and service accounts, who doesn't require tenant owner level permissions. +Consider a scenario, where a cluster admin creates a tenant and assigns ownership of the tenant to a user, the so-called tenant owner. Afterwards, tenant owner would in turn like to provide access to their cluster-scoped resources to a set of users (e.g. non-owners or tenant users), groups and service accounts, who doesn't require tenant-owner-level permissions. -Tenant Owner can provide access to following cluster-scoped resources to their tenant users, groups and service account by creating `ProxySetting` resource +Tenant Owner can provide access to the following cluster-scoped resources to their tenant users, groups and service account by creating `ProxySetting` resource - `Nodes` - `StorageClasses` - `IngressClasses` - `PriorityClasses` +- `RuntimeClasses` +- `PersistentVolumes` -Each Resource kind can be granted with following verbs, such as: +Each Resource kind can be granted with the following verbs, such as: - `List` - `Update` - `Delete` These tenant users, groups and services accounts have less privileged access than tenant owners. -As a Tenant Owner `alice`, you can create a `ProxySetting` resources to allow `bob` to list nodes, storage classes, ingress classes and priority classes +As a Tenant Owner `alice`, you can create a `ProxySetting` resource to allow `bob` to list nodes, storage classes, ingress classes and priority classes ```yaml apiVersion: capsule.clastix.io/v1beta2 kind: ProxySetting @@ -439,7 +569,7 @@ $ kubectl auth can-i --context bob-oidc@mycluster get priorityclasses yes ``` ## HTTP support -Capsule proxy supports `https` and `http`, although the latter is not recommended, we understand that it can be useful for some use cases (i.e. development, working behind a TLS-terminated reverse proxy and so on). As the default behaviour is to work with `https`, we need to use the flag `--enable-ssl=false` if we really want to work under `http`. +Capsule proxy supports `https` and `http`, although the latter is not recommended, we understand that it can be useful for some use cases (i.e. development, working behind a TLS-terminated reverse proxy and so on). As the default behaviour is to work with `https`, we need to use the flag `--enable-ssl=false` if we want to work under `http`. After having the `capsule-proxy` working under `http`, requests must provide authentication using an allowed Bearer Token. @@ -456,16 +586,16 @@ $ curl -H "Authorization: Bearer $TOKEN" http://localhost:9001/api/v1/namespaces Starting from the v0.3.0 release, Capsule Proxy exposes Prometheus metrics available at `http://0.0.0.0:8080/metrics`. -The offered metrics are related to the internal `controller-manager` code base, such as work-queue and REST client requests, and the Go runtime ones. +The offered metrics are related to the internal `controller-manager` code base, such as work queue and REST client requests, and the Go runtime ones. Along with these, metrics `capsule_proxy_response_time_seconds` and `capsule_proxy_requests_total` have been introduced and are specific to the Capsule Proxy code-base and functionalities. `capsule_proxy_response_time_seconds` offers a bucket representation of the HTTP request duration. -The available variables for this metrics are the following ones: -- `path`: the HTTP path of each single request that Capsule Proxy passes to the upstream +The available variables for these metrics are the following ones: +- `path`: the HTTP path of every single request that Capsule Proxy passes to the upstream `capsule_proxy_requests_total` counts the global requests that Capsule Proxy is passing to the upstream with the following labels. -- `path`: the HTTP path of each single request that Capsule Proxy passes to the upstream +- `path`: the HTTP path of every single request that Capsule Proxy passes to the upstream - `status`: the HTTP status code of the request > Example output of the metrics: @@ -493,6 +623,6 @@ The available variables for this metrics are the following ones: ## Contributing -`capsule-proxy` is an open-source software released with Apache2 [license](https://github.com/clastix/capsule-proxy/blob/master/LICENSE). +`capsule-proxy` is open-source software released with Apache2 [license](https://github.com/clastix/capsule-proxy/blob/master/LICENSE). Contributing guidelines are available [here](https://github.com/clastix/capsule-proxy/blob/master/CONTRIBUTING.md). From ff44aa17d1d2cc6be4dc16e868c3618395767a3c Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 4 Mar 2023 18:12:50 +0100 Subject: [PATCH 109/153] chore(helm): releasing capsule v0.3.0 --- charts/capsule/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index c1ae0934..8f60d29a 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -25,4 +25,4 @@ version: 0.4.0 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. -appVersion: 0.2.2 +appVersion: 0.3.0 From 0eff100c2170ae2eddcfeec1cade3b02f53e0414 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 4 Mar 2023 18:15:17 +0100 Subject: [PATCH 110/153] chore(kustomize): releasing capsule v0.3.0 --- config/install.yaml | 2 +- config/manager/kustomization.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/install.yaml b/config/install.yaml index 30e8ea64..27bb3313 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -2769,7 +2769,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: clastix/capsule:v0.2.1 + image: clastix/capsule:v0.3.0 imagePullPolicy: IfNotPresent name: manager ports: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 0bab283a..40d1a6e2 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -7,4 +7,4 @@ kind: Kustomization images: - name: controller newName: clastix/capsule - newTag: v0.2.1 + newTag: v0.3.0 From 03f89633098f6af6abfedb8d7552d4463271ceb5 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Sat, 4 Mar 2023 18:19:54 +0100 Subject: [PATCH 111/153] docs: documenting upgrade procedure for v0.3.0 --- docs/content/guides/upgrading.md | 52 +++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/docs/content/guides/upgrading.md b/docs/content/guides/upgrading.md index 6815009b..37495820 100644 --- a/docs/content/guides/upgrading.md +++ b/docs/content/guides/upgrading.md @@ -3,7 +3,8 @@ List of Tenant API changes: - [Capsule v0.1.0](https://github.com/clastix/capsule/releases/tag/v0.1.0) bump to `v1beta1` from `v1alpha1`. -- [Capsule v0.2.0](https://github.com/clastix/capsule/releases/tag/v0.1.0) bump to `v1beta2` from `v1beta1`, deprecating `v1alpha1`. +- [Capsule v0.2.0](https://github.com/clastix/capsule/releases/tag/v0.2.0) bump to `v1beta2` from `v1beta1`, deprecating `v1alpha1`. +- [Capsule v0.3.0](https://github.com/clastix/capsule/releases/tag/v0.3.0) missing enums required by [Capsule Proxy](https://github.com/clastix/capsule-proxy). This document aims to provide support and a guide on how to perform a clean upgrade to the latest API version in order to avoid service disruption and data loss. @@ -14,6 +15,55 @@ As an installation method, Helm is given for granted, YMMV using the `kustomize` We strongly suggest performing a full backup of your Kubernetes cluster, such as storage and etcd. Use your favourite tool according to your needs. +# Upgrading from v0.2.x to v0.3.x + +A minor bump has been requested due to some missing enums in the Tenant resource. + +## Scale down the Capsule controller + +Using the `kubectl` or Helm, scale down the Capsule controller manager: this is required to avoid the old Capsule version from processing objects that aren't yet installed as a CRD. + +``` +helm upgrade -n capsule-system capsule --set "replicaCount=0" +``` + +## Patch the Tenant custom resource definition + +Unfortunately, Helm doesn't manage the lifecycle of Custom Resource Definitions, additional details can be found [here](https://github.com/helm/community/blob/f9e06c16d89ccea1bea77c01a6a96ae3b309f823/architecture/crds.md). + +This process must be executed manually as follows: + +``` +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.3.0/config/crd/bases/tenant-crd.yaml +``` + +## Update your Capsule Helm chart + +Ensure to update the Capsule repository to fetch the latest changes. + +``` +helm repo update +``` + +The latest Chart must be used, at the current time, >=0.4.0 is expected for Capsule >=v0.3.0, you can fetch the full list of available charts with the following command. + +``` +helm search repo -l clastix/capsule +``` + +Since the Tenant custom resource definition has been patched with new fields, we can install back Capsule using the provided Helm chart. + +``` +helm upgrade --install capsule clastix/capsule -n capsule-system --create-namespace --version 0.4.0 +``` + +This will start the Operator with the latest changes, and perform the required sync operations like: + +1. Ensuring the CA is still valid +2. Ensuring a TLS certificate is valid for the local webhook server +3. If not using the cert-manager integration, patching the Validating and Mutating Webhook Configuration resources with the Capsule CA +4. If not using the cert-manager integration, patching the Capsule's Custom Resource Definitions conversion webhook fields with the Capsule CA + # Upgrading from v0.1.3 to v0.2.x ## Scale down the Capsule controller From 23e825a43eb80e6300375c994601138468ae243d Mon Sep 17 00:00:00 2001 From: zvlb Date: Sat, 4 Mar 2023 13:18:07 +0200 Subject: [PATCH 112/153] docs: add article about kubernetes dashboard Signed-off-by: zvlb --- .../assets/proxy-kubernetes-dashboard.png | Bin 0 -> 159534 bytes docs/content/guides/kubernetes-dashboard.md | 144 ++++++++++++++++++ docs/gridsome.server.js | 4 + 3 files changed, 148 insertions(+) create mode 100644 docs/content/guides/assets/proxy-kubernetes-dashboard.png create mode 100644 docs/content/guides/kubernetes-dashboard.md diff --git a/docs/content/guides/assets/proxy-kubernetes-dashboard.png b/docs/content/guides/assets/proxy-kubernetes-dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..58e45a05bebc27236aa497956e32a468c51cb617 GIT binary patch literal 159534 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 literal 0 HcmV?d00001 diff --git a/docs/content/guides/kubernetes-dashboard.md b/docs/content/guides/kubernetes-dashboard.md new file mode 100644 index 00000000..f7d80f0e --- /dev/null +++ b/docs/content/guides/kubernetes-dashboard.md @@ -0,0 +1,144 @@ +# Kubernetes Dashboard + +This guide describes how to integrate the [Kubernetes Dashboard](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/) and [Capsule Proxy](https://capsule.clastix.io/docs/general/proxy/) with OIDC authorization. + +In this guide, we will use [Keycloak](https://www.keycloak.org) as the Identity Provider. + +![Kubernetes Dashboard](./assets/proxy-kubernetes-dashboard.png) + +## Configuring oauth2-proxy + +To enable the proxy authorization from the Kubernetes dashboard to Keycloak, we need to use an OAuth proxy. +In this article, we will use [oauth2-proxy](https://oauth2-proxy.github.io/oauth2-proxy/) and install it as a pod in the Kubernetes Dashboard namespace. +Alternatively, we can install `oauth2-proxy` in a different namespace or use it as a sidecar container in the Kubernetes Dashboard deployment. + +Prepare the values for oauth2-proxy: +```bash +cat > values-oauth2-proxy.yaml < Values used for the config: +> +> - **OIDC_CLIENT_ID**: the keycloak client ID (name) which user in Kubernetes API Server for authorization +> - **OIDC_CLIENT_SECRET**: secret for the client (`OIDC_CLIENT_ID`). You can see it from the Keycloack UI -> Clients -> `OIDC_CLIENT_ID` -> Credentials +> - **DASHBOARD_URL**: the Kubernetes Dashboard URL +> - **KEYCLOAK_URL**: the Keycloak URL + +More information about the `keycloak-oidc` provider can be found on the [oauth2-proxy documentation](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#keycloak-oidc-auth-provider). + +We're ready to install the `oauth2-proxy`: + +```bash +helm repo add oauth2-proxy https://oauth2-proxy.github.io/manifests +helm install oauth2-proxy oauth2-proxy/oauth2-proxy -n ${KUBERNETES_DASHBOARD_NAMESPACE} -f values-oauth2-proxy.yaml +``` + +## Configuring Keycloak + +The Kubernetes cluster must be configured with a valid OIDC provider: for our guide, we're giving for granted that Keycloak is used, if you need more info please follow the [OIDC Authentication](/docs/guides/oidc-auth) section. + +In a such scenario, you should have in the `kube-apiserver.yaml` manifest the following content: +```yaml +spec: + containers: + - command: + - kube-apiserver + ... + - --oidc-issuer-url=https://${OIDC_ISSUER} + - --oidc-ca-file=/etc/kubernetes/oidc/ca.crt + - --oidc-client-id=${OIDC_CLIENT_ID} + - --oidc-username-claim=preferred_username + - --oidc-groups-claim=groups + - --oidc-username-prefix=- +``` + +Where `${OIDC_CLIENT_ID}` refers to the client ID that all tokens must be issued. + +For this client we need: +1. Check `Valid Redirect URIs`: in the `oauth2-proxy` configuration we set `redirect-url: "https://${DASHBOARD_URL}/oauth2/callback"`, it needs to add this path to the `Valid Redirect URIs` +2. Create a mapper with Mapper Type 'Group Membership' and Token Claim Name 'groups'. +3. Create a mapper with Mapper Type 'Audience' and Included Client Audience and Included Custom Audience set to your client name(OIDC_CLIENT_ID). + +## Configuring Kubernetes Dashboard + +If your Capsule Proxy uses HTTPS and the CA certificate is not the Kubernetes CA, you need to add a secret with the CA for the Capsule Proxy URL. +```bash +cat > ca.crt<< EOF +-----BEGIN CERTIFICATE----- +... +... +... +-----END CERTIFICATE----- +EOF + +kubectl create secret generic certificate --from-file=ca.crt=ca.crt -n ${KUBERNETES_DASHBOARD_NAMESPACE} +``` + +Prepare the values for the Kubernetes Dashboard: +```bash +cat > values-kubernetes-dashboard.yaml < Date: Tue, 7 Mar 2023 11:39:55 +0100 Subject: [PATCH 113/153] chore(adopters): adding Fastweb as adopter Co-authored-by: gcesilli --- ADOPTERS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 2e2a22a3..befafe5e 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -7,5 +7,8 @@ This is a list of companies that have adopted Capsule, feel free to open a Pull- ### [Bedag Informatik AG](https://www.bedag.ch/) ![Bedag](https://www.bedag.ch/wGlobal/wGlobal/layout/images/logo.svg) +### [Fastweb](https://www.fastweb.it/) +![Fastweb](https://www.fastweb.it/grandi-aziende/gfx/common/logo-fastweb-header.svg) + ### [Wargaming.net](https://www.wargaming.net/) ![Wargaming.net](https://static-cspbe-eu.wargaming.net/images/logo@2x.png) From de3849eba0368fa58c457ee3e67fd0d00b13b5ca Mon Sep 17 00:00:00 2001 From: Yarel Date: Thu, 23 Mar 2023 17:16:57 +0200 Subject: [PATCH 114/153] chore(adopters): adding Velocity as adopter --- ADOPTERS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index befafe5e..b2f15470 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -10,5 +10,8 @@ This is a list of companies that have adopted Capsule, feel free to open a Pull- ### [Fastweb](https://www.fastweb.it/) ![Fastweb](https://www.fastweb.it/grandi-aziende/gfx/common/logo-fastweb-header.svg) +### [Velocity](https://velocity.tech/) +![Velocity](https://raw.githubusercontent.com/yarelm/velocity-logo/main/velocity.png) + ### [Wargaming.net](https://www.wargaming.net/) ![Wargaming.net](https://static-cspbe-eu.wargaming.net/images/logo@2x.png) From 78b0c32056a9c5787a2fb64ca92f54997a4037ff Mon Sep 17 00:00:00 2001 From: Sagar Jadhav Date: Thu, 23 Mar 2023 20:49:25 +0530 Subject: [PATCH 115/153] fix: users can patch namespaces not managed by capsule Signed-off-by: Sagar Jadhav --- pkg/webhook/namespace/owner_reference.go | 4 ++++ pkg/webhook/namespace/patch.go | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/webhook/namespace/owner_reference.go b/pkg/webhook/namespace/owner_reference.go index b38dd40c..7f4e2960 100644 --- a/pkg/webhook/namespace/owner_reference.go +++ b/pkg/webhook/namespace/owner_reference.go @@ -42,6 +42,10 @@ func (r *ownerReferenceHandler) OnUpdate(_ client.Client, decoder *admission.Dec return utils.ErroredResponse(err) } + if len(oldNs.OwnerReferences) == 0 { + return nil + } + newNs := &corev1.Namespace{} if err := decoder.Decode(req, newNs); err != nil { return utils.ErroredResponse(err) diff --git a/pkg/webhook/namespace/patch.go b/pkg/webhook/namespace/patch.go index 18f1e972..1f3fb18e 100644 --- a/pkg/webhook/namespace/patch.go +++ b/pkg/webhook/namespace/patch.go @@ -72,11 +72,6 @@ func (r *patchHandler) OnUpdate(c client.Client, decoder *admission.Decoder, rec return &response } - } else { - recorder.Eventf(ns, corev1.EventTypeWarning, "NamespacePatch", e) - response := admission.Denied(e) - - return &response } return nil From c355f0d4cef837c48937492800ddb103ec4428ef Mon Sep 17 00:00:00 2001 From: vixns Date: Sun, 26 Mar 2023 15:52:21 +0200 Subject: [PATCH 116/153] docs: dashboard guide updates * kubernetes dashboard guide fixes. * Update dashboard ingress annotations --- docs/content/guides/kubernetes-dashboard.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/guides/kubernetes-dashboard.md b/docs/content/guides/kubernetes-dashboard.md index f7d80f0e..567b109a 100644 --- a/docs/content/guides/kubernetes-dashboard.md +++ b/docs/content/guides/kubernetes-dashboard.md @@ -111,7 +111,7 @@ extraVolumes: - key: ca.crt path: ca.crt extraVolumeMounts: - - mountPath: /var/run/secrets/kubernetes.io/serviceaccount/ + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: token-ca ingress: @@ -119,6 +119,7 @@ ingress: annotations: nginx.ingress.kubernetes.io/auth-signin: https://${DASHBOARD_URL}/oauth2/start?rd=$escaped_request_uri nginx.ingress.kubernetes.io/auth-url: https://${DASHBOARD_URL}/oauth2/auth + nginx.ingress.kubernetes.io/auth-response-headers: "authorization" hosts: - ${DASHBOARD_URL} tls: From 65d5b248969eeddd6208b4beb88d6cb18dc7b82d Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 23 Mar 2023 18:39:51 +0100 Subject: [PATCH 117/153] feat: blocking replicated resources write ops by tenant owners --- controllers/resources/processor.go | 3 +- main.go | 2 + pkg/indexer/indexer.go | 3 + pkg/indexer/tenantresource/constants.go | 8 +++ pkg/indexer/tenantresource/global.go | 34 +++++++++ pkg/indexer/tenantresource/local.go | 34 +++++++++ pkg/webhook/route/tenantresource_objs.go | 26 +++++++ pkg/webhook/tenantresource/objects.go | 89 ++++++++++++++++++++++++ 8 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 pkg/indexer/tenantresource/constants.go create mode 100644 pkg/indexer/tenantresource/global.go create mode 100644 pkg/indexer/tenantresource/local.go create mode 100644 pkg/webhook/route/tenantresource_objs.go create mode 100644 pkg/webhook/tenantresource/objects.go diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go index b9fd2870..37388c49 100644 --- a/controllers/resources/processor.go +++ b/controllers/resources/processor.go @@ -26,6 +26,7 @@ import ( ) const ( + Label = "capsule.clastix.io/resources" finalizer = "capsule.clastix.io/resources" ) @@ -121,7 +122,7 @@ func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant objAnnotations[tenantLabel] = tnt.GetName() - objLabels["capsule.clastix.io/resources"] = fmt.Sprintf("%d", resourceIndex) + objLabels[Label] = fmt.Sprintf("%d", resourceIndex) objLabels[tenantLabel] = tnt.GetName() // processed will contain the sets of resources replicated, both for the raw and the Namespaced ones: // these are required to perform a final pruning once the replication has been occurred. diff --git a/main.go b/main.go index 4a35637e..4e989998 100644 --- a/main.go +++ b/main.go @@ -50,6 +50,7 @@ import ( "github.com/clastix/capsule/pkg/webhook/route" "github.com/clastix/capsule/pkg/webhook/service" "github.com/clastix/capsule/pkg/webhook/tenant" + tntresource "github.com/clastix/capsule/pkg/webhook/tenantresource" "github.com/clastix/capsule/pkg/webhook/utils" ) @@ -245,6 +246,7 @@ func main() { route.Ingress(ingress.Class(cfg, kubeVersion), ingress.Hostnames(cfg), ingress.Collision(cfg), ingress.Wildcard()), route.PVC(pvc.Validating(), pvc.PersistentVolumeReuse()), route.Service(service.Handler()), + route.TenantResourceObjects(utils.InCapsuleGroups(cfg, tntresource.WriteOpsHandler())), route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())), route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler(), tenant.ForbiddenAnnotationsRegexHandler(), tenant.ProtectedHandler()), route.OwnerReference(utils.InCapsuleGroups(cfg, namespacewebhook.OwnerReferenceHandler(), ownerreference.Handler(cfg))), diff --git a/pkg/indexer/indexer.go b/pkg/indexer/indexer.go index 4dde3501..c2b3cdb5 100644 --- a/pkg/indexer/indexer.go +++ b/pkg/indexer/indexer.go @@ -20,6 +20,7 @@ import ( "github.com/clastix/capsule/pkg/indexer/ingress" "github.com/clastix/capsule/pkg/indexer/namespace" "github.com/clastix/capsule/pkg/indexer/tenant" + "github.com/clastix/capsule/pkg/indexer/tenantresource" ) type CustomIndexer interface { @@ -36,6 +37,8 @@ func AddToManager(ctx context.Context, log logr.Logger, mgr manager.Manager) err ingress.HostnamePath{Obj: &extensionsv1beta1.Ingress{}}, ingress.HostnamePath{Obj: &networkingv1beta1.Ingress{}}, ingress.HostnamePath{Obj: &networkingv1.Ingress{}}, + tenantresource.GlobalProcessedItems{}, + tenantresource.LocalProcessedItems{}, } for _, f := range indexers { diff --git a/pkg/indexer/tenantresource/constants.go b/pkg/indexer/tenantresource/constants.go new file mode 100644 index 00000000..254622f9 --- /dev/null +++ b/pkg/indexer/tenantresource/constants.go @@ -0,0 +1,8 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package tenantresource + +const ( + IndexerFieldName = "status.processedItems" +) diff --git a/pkg/indexer/tenantresource/global.go b/pkg/indexer/tenantresource/global.go new file mode 100644 index 00000000..380d9eb8 --- /dev/null +++ b/pkg/indexer/tenantresource/global.go @@ -0,0 +1,34 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +//nolint:dupl +package tenantresource + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +type GlobalProcessedItems struct{} + +func (g GlobalProcessedItems) Object() client.Object { + return &capsulev1beta2.GlobalTenantResource{} +} + +func (g GlobalProcessedItems) Field() string { + return IndexerFieldName +} + +func (g GlobalProcessedItems) Func() client.IndexerFunc { + return func(object client.Object) []string { + tgr := object.(*capsulev1beta2.GlobalTenantResource) //nolint:forcetypeassert + + out := make([]string, 0, len(tgr.Status.ProcessedItems)) + for _, pi := range tgr.Status.ProcessedItems { + out = append(out, pi.String()) + } + + return out + } +} diff --git a/pkg/indexer/tenantresource/local.go b/pkg/indexer/tenantresource/local.go new file mode 100644 index 00000000..9413400c --- /dev/null +++ b/pkg/indexer/tenantresource/local.go @@ -0,0 +1,34 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +//nolint:dupl +package tenantresource + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" +) + +type LocalProcessedItems struct{} + +func (g LocalProcessedItems) Object() client.Object { + return &capsulev1beta2.TenantResource{} +} + +func (g LocalProcessedItems) Field() string { + return IndexerFieldName +} + +func (g LocalProcessedItems) Func() client.IndexerFunc { + return func(object client.Object) []string { + tgr := object.(*capsulev1beta2.TenantResource) //nolint:forcetypeassert + + out := make([]string, 0, len(tgr.Status.ProcessedItems)) + for _, pi := range tgr.Status.ProcessedItems { + out = append(out, pi.String()) + } + + return out + } +} diff --git a/pkg/webhook/route/tenantresource_objs.go b/pkg/webhook/route/tenantresource_objs.go new file mode 100644 index 00000000..b1e8e3cc --- /dev/null +++ b/pkg/webhook/route/tenantresource_objs.go @@ -0,0 +1,26 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package route + +import ( + capsulewebhook "github.com/clastix/capsule/pkg/webhook" +) + +// +kubebuilder:webhook:path=/tenantresource-objects,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="*",resources="*",verbs=update;delete,versions="*",name=resource-objects.tenant.capsule.clastix.io + +type tntResourceObjs struct { + handlers []capsulewebhook.Handler +} + +func TenantResourceObjects(handlers ...capsulewebhook.Handler) capsulewebhook.Webhook { + return &tntResourceObjs{handlers: handlers} +} + +func (t tntResourceObjs) GetPath() string { + return "/tenantresource-objects" +} + +func (t tntResourceObjs) GetHandlers() []capsulewebhook.Handler { + return t.handlers +} diff --git a/pkg/webhook/tenantresource/objects.go b/pkg/webhook/tenantresource/objects.go new file mode 100644 index 00000000..6398d921 --- /dev/null +++ b/pkg/webhook/tenantresource/objects.go @@ -0,0 +1,89 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import ( + "context" + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/indexer/tenantresource" + capsulewebhook "github.com/clastix/capsule/pkg/webhook" + "github.com/clastix/capsule/pkg/webhook/utils" +) + +type cordoningHandler struct{} + +func WriteOpsHandler() capsulewebhook.Handler { + return &cordoningHandler{} +} + +func (h *cordoningHandler) handler(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder) *admission.Response { + tntList := &capsulev1beta2.TenantList{} + + if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace)}); err != nil { + return utils.ErroredResponse(err) + } + // resource is not inside a Tenant namespace: + // we can avoid any kind of extra check. + if len(tntList.Items) == 0 { + return nil + } + // Checking if the object is managed by a TenantResource, local or global + ors := capsulev1beta2.ObjectReferenceStatus{ + ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{ + Kind: req.Kind.Kind, + Namespace: req.Namespace, + APIVersion: req.Kind.Version, + }, + Name: req.Name, + } + + global, local := &capsulev1beta2.GlobalTenantResourceList{}, &capsulev1beta2.TenantResourceList{} + + if err := clt.List(ctx, global, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(tenantresource.IndexerFieldName, ors.String())}); err != nil { + return utils.ErroredResponse(err) + } + + if err := clt.List(ctx, local, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(tenantresource.IndexerFieldName, ors.String())}); err != nil { + return utils.ErroredResponse(err) + } + + if len(local.Items) > 0 || len(global.Items) > 0 { + tnt := tntList.Items[0] + + recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantResourceWriteOp", "%s %s/%s cannot be %sd, resource is managed by the Tenant", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation))) + + response := admission.Denied(fmt.Sprintf("resource %s is managed at the Tenant level", req.Name)) + + return &response + } + + return nil +} + +func (h *cordoningHandler) OnCreate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return nil + } +} + +func (h *cordoningHandler) OnDelete(client client.Client, _ *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return h.handler(ctx, client, req, recorder) + } +} + +func (h *cordoningHandler) OnUpdate(client client.Client, _ *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return h.handler(ctx, client, req, recorder) + } +} From 717da87d0c55af9e39f00aff564eb1786a611f09 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 23 Mar 2023 18:40:40 +0100 Subject: [PATCH 118/153] test(e2e): blocking replicated resources write ops by tenant owners --- e2e/tenantresource_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/e2e/tenantresource_test.go b/e2e/tenantresource_test.go index 76847f82..b3afe88c 100644 --- a/e2e/tenantresource_test.go +++ b/e2e/tenantresource_test.go @@ -296,6 +296,36 @@ var _ = Describe("Creating a TenantResource object", func() { } }) + By("checking replicated object cannot be deleted by a Tenant Owner", func() { + for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} { + cs := ownerClient(solar.Spec.Owners[0]) + + Consistently(func() error { + return cs.CoreV1().Secrets("solar-three").Delete(context.TODO(), name, metav1.DeleteOptions{}) + }, 10*time.Second, time.Second).Should(HaveOccurred()) + } + }) + + By("checking replicated object cannot be update by a Tenant Owner", func() { + for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} { + cs := ownerClient(solar.Spec.Owners[0]) + + Consistently(func() error { + secret, err := cs.CoreV1().Secrets("solar-three").Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return err + } + + secret.SetLabels(nil) + secret.SetAnnotations(nil) + + _, err = cs.CoreV1().Secrets("solar-three").Update(context.TODO(), secret, metav1.UpdateOptions{}) + + return err + }, 10*time.Second, time.Second).Should(HaveOccurred()) + } + }) + By("checking that cross-namespace objects are not replicated", func() { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tr.GetName(), Namespace: "solar-system"}, tr)).ToNot(HaveOccurred()) tr.Spec.Resources[0].NamespacedItems = append(tr.Spec.Resources[0].NamespacedItems, capsulev1beta2.ObjectReference{ From 79abb1f0ab39f27d665bc0af68d8264653dc2c8b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 23 Mar 2023 18:41:38 +0100 Subject: [PATCH 119/153] chore(kustomize): blocking replicated resources write ops by tenant owners --- config/install.yaml | 29 +++++++++++++++++++ config/webhook/manifests.yaml | 20 +++++++++++++ .../webhook/patch_validating_ns_selector.yaml | 15 ++++++++++ 3 files changed, 64 insertions(+) diff --git a/config/install.yaml b/config/install.yaml index 27bb3313..56792783 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -3112,6 +3112,35 @@ webhooks: - services scope: Namespaced sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: capsule-webhook-service + namespace: capsule-system + path: /tenantresource-objects + failurePolicy: Fail + name: resource-objects.tenant.capsule.clastix.io + namespaceSelector: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists + objectSelector: + matchExpressions: + - key: capsule.clastix.io/resources + operator: Exists + rules: + - apiGroups: + - '*' + apiVersions: + - '*' + operations: + - UPDATE + - DELETE + resources: + - '*' + scope: Namespaced + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index f82891d2..294c5fc2 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -253,6 +253,26 @@ webhooks: resources: - services sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /tenantresource-objects + failurePolicy: Fail + name: resource-objects.tenant.capsule.clastix.io + rules: + - apiGroups: + - '*' + apiVersions: + - '*' + operations: + - UPDATE + - DELETE + resources: + - '*' + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/config/webhook/patch_validating_ns_selector.yaml b/config/webhook/patch_validating_ns_selector.yaml index 523f7a0d..2e39ab83 100644 --- a/config/webhook/patch_validating_ns_selector.yaml +++ b/config/webhook/patch_validating_ns_selector.yaml @@ -40,6 +40,18 @@ matchExpressions: - key: capsule.clastix.io/tenant operator: Exists +- op: add + path: /webhooks/8/namespaceSelector + value: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists +- op: add + path: /webhooks/8/objectSelector + value: + matchExpressions: + - key: capsule.clastix.io/resources + operator: Exists - op: add path: /webhooks/0/rules/0/scope value: Namespaced @@ -58,3 +70,6 @@ - op: add path: /webhooks/7/rules/0/scope value: Namespaced +- op: add + path: /webhooks/8/rules/0/scope + value: Namespaced From 1c73deab4d77059723fba28fcd886abacd2e517f Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 23 Mar 2023 18:41:53 +0100 Subject: [PATCH 120/153] chore(helm): blocking replicated resources write ops by tenant owners --- charts/capsule/README.md | 1 + .../validatingwebhookconfiguration.yaml | 32 +++++++++++++++++++ charts/capsule/values.yaml | 2 ++ 3 files changed, 35 insertions(+) diff --git a/charts/capsule/README.md b/charts/capsule/README.md index 2ad95348..c749e357 100644 --- a/charts/capsule/README.md +++ b/charts/capsule/README.md @@ -158,6 +158,7 @@ Here the values you can override: | webhooks.services.failurePolicy | string | `"Fail"` | | | webhooks.services.namespaceSelector.matchExpressions[0].key | string | `"capsule.clastix.io/tenant"` | | | webhooks.services.namespaceSelector.matchExpressions[0].operator | string | `"Exists"` | | +| webhooks.tenantResourceObjects.failurePolicy | string | `"Fail"` | | | webhooks.tenants.failurePolicy | string | `"Fail"` | | ## Created resources diff --git a/charts/capsule/templates/validatingwebhookconfiguration.yaml b/charts/capsule/templates/validatingwebhookconfiguration.yaml index 80b80c03..58518ac0 100644 --- a/charts/capsule/templates/validatingwebhookconfiguration.yaml +++ b/charts/capsule/templates/validatingwebhookconfiguration.yaml @@ -257,6 +257,38 @@ webhooks: scope: Namespaced sideEffects: None timeoutSeconds: {{ .Values.validatingWebhooksTimeoutSeconds }} +- admissionReviewVersions: + - v1 + clientConfig: +{{- if not .Values.certManager.generateCertificates }} + caBundle: Cg== +{{- end }} + service: + name: capsule-webhook-service + namespace: capsule-system + path: /tenantresource-objects + failurePolicy: {{ .Values.webhooks.tenantResourceObjects.failurePolicy }} + name: resource-objects.tenant.capsule.clastix.io + namespaceSelector: + matchExpressions: + - key: capsule.clastix.io/tenant + operator: Exists + objectSelector: + matchExpressions: + - key: capsule.clastix.io/resources + operator: Exists + rules: + - apiGroups: + - '*' + apiVersions: + - '*' + operations: + - UPDATE + - DELETE + resources: + - '*' + scope: Namespaced + sideEffects: None - admissionReviewVersions: - v1 - v1beta1 diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index ebe7921a..f086dc1a 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -188,6 +188,8 @@ webhooks: operator: Exists tenants: failurePolicy: Fail + tenantResourceObjects: + failurePolicy: Fail services: failurePolicy: Fail namespaceSelector: From a94123db890a3a9ab4ba82878a3f0c7283d15f8c Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 28 Mar 2023 12:22:33 +0200 Subject: [PATCH 121/153] fix: enforcing namespace for default mutators --- pkg/webhook/defaults/handler.go | 6 +++--- pkg/webhook/defaults/ingress.go | 4 +++- pkg/webhook/defaults/pods.go | 4 +++- pkg/webhook/defaults/storage.go | 4 +++- pkg/webhook/ingress/types.go | 13 +++++++++++++ 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/pkg/webhook/defaults/handler.go b/pkg/webhook/defaults/handler.go index 5a3a4f1e..57b38492 100644 --- a/pkg/webhook/defaults/handler.go +++ b/pkg/webhook/defaults/handler.go @@ -51,11 +51,11 @@ func (h *handler) mutate(ctx context.Context, req admission.Request, c client.Cl switch { case req.Resource == (metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}): - response = mutatePodDefaults(ctx, req, c, decoder, recorder) + response = mutatePodDefaults(ctx, req, c, decoder, recorder, req.Namespace) case req.Resource == (metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumeclaims"}): - response = mutatePVCDefaults(ctx, req, c, decoder, recorder) + response = mutatePVCDefaults(ctx, req, c, decoder, recorder, req.Namespace) case req.Resource == (metav1.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "ingresses"}) || req.Resource == (metav1.GroupVersionResource{Group: "networking.k8s.io", Version: "v1beta1", Resource: "ingresses"}): - response = mutateIngressDefaults(ctx, req, h.version, c, decoder, recorder) + response = mutateIngressDefaults(ctx, req, h.version, c, decoder, recorder, req.Namespace) } if response == nil { diff --git a/pkg/webhook/defaults/ingress.go b/pkg/webhook/defaults/ingress.go index 8ec687cd..7038245c 100644 --- a/pkg/webhook/defaults/ingress.go +++ b/pkg/webhook/defaults/ingress.go @@ -20,12 +20,14 @@ import ( "github.com/clastix/capsule/pkg/webhook/utils" ) -func mutateIngressDefaults(ctx context.Context, req admission.Request, version *version.Version, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response { +func mutateIngressDefaults(ctx context.Context, req admission.Request, version *version.Version, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder, namespace string) *admission.Response { ingress, err := capsuleingress.FromRequest(req, decoder) if err != nil { return utils.ErroredResponse(err) } + ingress.SetNamespace(namespace) + var tnt *capsulev1beta2.Tenant tnt, err = capsuleingress.TenantFromIngress(ctx, c, ingress) diff --git a/pkg/webhook/defaults/pods.go b/pkg/webhook/defaults/pods.go index 7b1036f7..3e5ff455 100644 --- a/pkg/webhook/defaults/pods.go +++ b/pkg/webhook/defaults/pods.go @@ -18,7 +18,7 @@ import ( "github.com/clastix/capsule/pkg/webhook/utils" ) -func mutatePodDefaults(ctx context.Context, req admission.Request, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response { +func mutatePodDefaults(ctx context.Context, req admission.Request, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder, namespace string) *admission.Response { var err error pod := &corev1.Pod{} @@ -26,6 +26,8 @@ func mutatePodDefaults(ctx context.Context, req admission.Request, c client.Clie return utils.ErroredResponse(err) } + pod.SetNamespace(namespace) + var tnt *capsulev1beta2.Tenant tnt, err = utils.TenantByStatusNamespace(ctx, c, pod.Namespace) diff --git a/pkg/webhook/defaults/storage.go b/pkg/webhook/defaults/storage.go index e6c12041..2513524b 100644 --- a/pkg/webhook/defaults/storage.go +++ b/pkg/webhook/defaults/storage.go @@ -18,7 +18,7 @@ import ( "github.com/clastix/capsule/pkg/webhook/utils" ) -func mutatePVCDefaults(ctx context.Context, req admission.Request, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) *admission.Response { +func mutatePVCDefaults(ctx context.Context, req admission.Request, c client.Client, decoder *admission.Decoder, recorder record.EventRecorder, namespace string) *admission.Response { var err error pvc := &corev1.PersistentVolumeClaim{} @@ -26,6 +26,8 @@ func mutatePVCDefaults(ctx context.Context, req admission.Request, c client.Clie return utils.ErroredResponse(err) } + pvc.SetNamespace(namespace) + var tnt *capsulev1beta2.Tenant tnt, err = utils.TenantByStatusNamespace(ctx, c, pvc.Namespace) diff --git a/pkg/webhook/ingress/types.go b/pkg/webhook/ingress/types.go index bfc19a9a..3bf84eb0 100644 --- a/pkg/webhook/ingress/types.go +++ b/pkg/webhook/ingress/types.go @@ -22,6 +22,7 @@ type Ingress interface { Name() string HostnamePathsPairs() map[string]sets.String SetIngressClass(string) + SetNamespace(string) } type NetworkingV1 struct { @@ -63,6 +64,10 @@ func (n NetworkingV1) Namespace() string { return n.GetNamespace() } +func (n NetworkingV1) SetNamespace(ns string) { + n.Ingress.SetNamespace(ns) +} + //nolint:dupl func (n NetworkingV1) HostnamePathsPairs() (pairs map[string]sets.String) { pairs = make(map[string]sets.String) @@ -129,6 +134,10 @@ func (n NetworkingV1Beta1) Namespace() string { return n.GetNamespace() } +func (n NetworkingV1Beta1) SetNamespace(ns string) { + n.Ingress.SetNamespace(ns) +} + //nolint:dupl func (n NetworkingV1Beta1) HostnamePathsPairs() (pairs map[string]sets.String) { pairs = make(map[string]sets.String) @@ -164,6 +173,10 @@ func (e Extension) Name() string { return e.GetName() } +func (e Extension) SetNamespace(ns string) { + e.Ingress.SetNamespace(ns) +} + func (e Extension) IngressClass() (res *string) { res = e.Spec.IngressClassName if res == nil { From 954b4da3f40e45b44b4f2d3b5134d5fb7f68c961 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 28 Mar 2023 12:27:16 +0200 Subject: [PATCH 122/153] chore(ci): ubuntu version pinning to 20.04 --- .github/workflows/ci.yml | 6 +++--- .github/workflows/e2e.yml | 2 +- .github/workflows/gosec.yml | 2 +- .github/workflows/helm.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbbd6947..216e09b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: jobs: commit_lint: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: @@ -18,7 +18,7 @@ jobs: firstParent: true golangci: name: lint - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Run golangci-lint @@ -29,7 +29,7 @@ jobs: args: --timeout 5m --config .golangci.yml diff: name: diff - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 6ed88a03..910d2f14 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -33,7 +33,7 @@ jobs: fail-fast: false matrix: k8s-version: ['v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.1'] - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/gosec.yml b/.github/workflows/gosec.yml index cf4b2b80..6c886d20 100644 --- a/.github/workflows/gosec.yml +++ b/.github/workflows/gosec.yml @@ -6,7 +6,7 @@ on: branches: [ "*" ] jobs: tests: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 env: GO111MODULE: on steps: diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 69a7b0ae..539dd1a4 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -9,7 +9,7 @@ on: jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: @@ -69,7 +69,7 @@ jobs: # if: steps.list-changed.outputs.changed == 'true' release: if: startsWith(github.ref, 'refs/tags/helm-v') - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Publish Helm chart From 07f479a5dc01447db1f1e56766d52574a2f522d4 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 21 Mar 2023 17:15:40 +0100 Subject: [PATCH 123/153] fix(docs): wrong manifests urls for migration --- docs/content/guides/upgrading.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/guides/upgrading.md b/docs/content/guides/upgrading.md index 37495820..caa6f1a9 100644 --- a/docs/content/guides/upgrading.md +++ b/docs/content/guides/upgrading.md @@ -34,7 +34,7 @@ Unfortunately, Helm doesn't manage the lifecycle of Custom Resource Definitions, This process must be executed manually as follows: ``` -kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.3.0/config/crd/bases/tenant-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.3.0/charts/capsule/crds/tenant-crd.yaml ``` ## Update your Capsule Helm chart @@ -90,7 +90,7 @@ The deletion of the `CapsuleConfiguration` resource is required, along with the ``` kubectl delete capsuleconfiguration default -kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/config/crd/bases/capsuleconfiguration-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/charts/capsule/crds/capsuleconfiguration-crd.yaml ``` During the Helm upgrade, a new `CapsuleConfiguration` will be created: please, refer to the Helm Chart values to pick up your desired settings. @@ -102,9 +102,9 @@ Unfortunately, Helm doesn't manage the lifecycle of Custom Resource Definitions, This process must be executed manually as follows: ``` -kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/config/crd/bases/globaltenantresources-crd.yaml -kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/config/crd/bases/tenant-crd.yaml -kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/config/crd/bases/tenantresources-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/charts/capsule/crds/globaltenantresources-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/charts/capsule/crds/tenant-crd.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.2.1/charts/capsule/crds/tenantresources-crd.yaml ``` > We're giving for granted that Capsule is installed in the `capsule-system` Namespace. @@ -192,7 +192,7 @@ Unfortunately, Helm doesn't manage the lifecycle of Custom Resource Definitions, This process must be executed manually as follows: ``` -kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.1.0/config/crd/bases/capsule.clastix.io_tenants.yaml +kubectl apply -f https://raw.githubusercontent.com/clastix/capsule/v0.1.0/charts/capsule/crds/tenant-crd.yaml ``` > Please note the Capsule version in the said URL, your mileage may vary according to the desired upgrading version. From 6eb7f90539a1b0f2d7fd67e6c1d77b896b457900 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 28 Mar 2023 13:00:33 +0200 Subject: [PATCH 124/153] chore(kustomize): releasing v0.3.1 --- config/install.yaml | 2 +- config/manager/kustomization.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/install.yaml b/config/install.yaml index 56792783..ffe27a14 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -2769,7 +2769,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: clastix/capsule:v0.3.0 + image: clastix/capsule:v0.3.1 imagePullPolicy: IfNotPresent name: manager ports: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 40d1a6e2..1f8911a3 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -7,4 +7,4 @@ kind: Kustomization images: - name: controller newName: clastix/capsule - newTag: v0.3.0 + newTag: v0.3.1 From d5d4c8d2b68e52a64d49092444a5e7a7bd44362e Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 28 Mar 2023 13:00:42 +0200 Subject: [PATCH 125/153] chore(helm): releasing v0.3.1 --- charts/capsule/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 8f60d29a..de2a932c 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,8 +21,8 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.4.0 +version: 0.4.1 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. -appVersion: 0.3.0 +appVersion: 0.3.1 From a9106a32254476e60d2caac8dba0d819b3d17df1 Mon Sep 17 00:00:00 2001 From: Fulvio Risso Date: Wed, 29 Mar 2023 16:26:19 +0200 Subject: [PATCH 126/153] chore(adopters): adding POLITO to adopters --- ADOPTERS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index b2f15470..b64400d8 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -10,6 +10,9 @@ This is a list of companies that have adopted Capsule, feel free to open a Pull- ### [Fastweb](https://www.fastweb.it/) ![Fastweb](https://www.fastweb.it/grandi-aziende/gfx/common/logo-fastweb-header.svg) +### [Politecnico di Torino](https://www.polito.it/) +![Politecnico di Torino](https://www.polito.it/themes/custom/polito/logo.svg) + ### [Velocity](https://velocity.tech/) ![Velocity](https://raw.githubusercontent.com/yarelm/velocity-logo/main/velocity.png) From 4ac65ae57b4cf87fbb085f5cd696c40e70beece4 Mon Sep 17 00:00:00 2001 From: mocini-reevo <126001025+mocini-reevo@users.noreply.github.com> Date: Mon, 3 Apr 2023 18:49:02 +0200 Subject: [PATCH 127/153] chore(adopters): adding reevo as an adopter --- ADOPTERS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index b64400d8..b3987eb0 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -13,6 +13,9 @@ This is a list of companies that have adopted Capsule, feel free to open a Pull- ### [Politecnico di Torino](https://www.polito.it/) ![Politecnico di Torino](https://www.polito.it/themes/custom/polito/logo.svg) +### [Reevo](https://www.reevo.it/) +![Reevo Cloud and CyberSecurity](https://www.dropbox.com/s/x3q6r0oqstgvtdr/Logo_ReeVo_270x200px.svg) + ### [Velocity](https://velocity.tech/) ![Velocity](https://raw.githubusercontent.com/yarelm/velocity-logo/main/velocity.png) From 46a8d212fcf73b4bba53c88255c7ea84ed412a7d Mon Sep 17 00:00:00 2001 From: Iacopo Colonnelli Date: Wed, 5 Apr 2023 11:28:29 +0200 Subject: [PATCH 128/153] chore(maintainers): added unito to the list of adopters --- ADOPTERS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index b3987eb0..732d047a 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -16,6 +16,9 @@ This is a list of companies that have adopted Capsule, feel free to open a Pull- ### [Reevo](https://www.reevo.it/) ![Reevo Cloud and CyberSecurity](https://www.dropbox.com/s/x3q6r0oqstgvtdr/Logo_ReeVo_270x200px.svg) +### [University of Torino](https://www.unito.it) +![University of Torino](https://www.unito.it/sites/all/themes/bsunito/img/logo_new_2022.svg) + ### [Velocity](https://velocity.tech/) ![Velocity](https://raw.githubusercontent.com/yarelm/velocity-logo/main/velocity.png) From 953cfdc172dd1dd93646dd8609cda61071c8528a Mon Sep 17 00:00:00 2001 From: Maksim Fedotov Date: Mon, 17 Apr 2023 18:26:44 +0300 Subject: [PATCH 129/153] feat(helm): allow to configure nodeMetadata for CapsuleConfiguration --- charts/capsule/Chart.yaml | 2 +- charts/capsule/README.md | 1 + charts/capsule/templates/configuration-default.yaml | 4 ++++ charts/capsule/values.yaml | 8 ++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index de2a932c..e57f6fbb 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,7 +21,7 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.4.1 +version: 0.4.2 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. diff --git a/charts/capsule/README.md b/charts/capsule/README.md index c749e357..d7e0040d 100644 --- a/charts/capsule/README.md +++ b/charts/capsule/README.md @@ -101,6 +101,7 @@ Here the values you can override: | manager.options.forceTenantPrefix | bool | `false` | Boolean, enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash | | manager.options.generateCertificates | bool | `true` | Specifies whether capsule webhooks certificates should be generated by capsule operator | | manager.options.logLevel | string | `"4"` | Set the log verbosity of the capsule with a value from 1 to 10 | +| manager.options.nodeMetadata | object | `{"forbiddenAnnotations":{"denied":[],"deniedRegex":""},"forbiddenLabels":{"denied":[],"deniedRegex":""}}` | Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant | | manager.options.protectedNamespaceRegex | string | `""` | If specified, disallows creation of namespaces matching the passed regexp | | manager.readinessProbe | object | `{"httpGet":{"path":"/readyz","port":10080}}` | Configure the readiness probe using Deployment probe spec | | manager.resources.limits.cpu | string | `"200m"` | | diff --git a/charts/capsule/templates/configuration-default.yaml b/charts/capsule/templates/configuration-default.yaml index 1356fd58..bd5b6513 100644 --- a/charts/capsule/templates/configuration-default.yaml +++ b/charts/capsule/templates/configuration-default.yaml @@ -20,3 +20,7 @@ spec: - {{ . }} {{- end}} protectedNamespaceRegex: {{ .Values.manager.options.protectedNamespaceRegex | quote }} + {{- with .Values.manager.options.nodeMetadata }} + nodeMetadata: + {{- toYaml . | nindent 4 }} + {{- end }} diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index f086dc1a..215a3426 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -51,6 +51,14 @@ manager: protectedNamespaceRegex: "" # -- Specifies whether capsule webhooks certificates should be generated by capsule operator generateCertificates: true + # -- Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant + nodeMetadata: + forbiddenLabels: + denied: [] + deniedRegex: "" + forbiddenAnnotations: + denied: [] + deniedRegex: "" # -- Configure the liveness probe using Deployment probe spec livenessProbe: From e15773e81191194aa685c5df5240a6bd46593125 Mon Sep 17 00:00:00 2001 From: Max Fedotov Date: Thu, 20 Apr 2023 17:30:23 +0300 Subject: [PATCH 130/153] fix: missing v1beta2 version for CRD ConversionReviewVersions --- controllers/tls/manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/tls/manager.go b/controllers/tls/manager.go index c2cafa25..d4566bbb 100644 --- a/controllers/tls/manager.go +++ b/controllers/tls/manager.go @@ -240,7 +240,7 @@ func (r *Reconciler) updateTenantCustomResourceDefinition(ctx context.Context, n }, CABundle: caBundle, }, - ConversionReviewVersions: []string{"v1alpha1", "v1beta1"}, + ConversionReviewVersions: []string{"v1alpha1", "v1beta1", "v1beta2"}, }, } From 1d86857e928adcf31395ca94a81eb282ea012e41 Mon Sep 17 00:00:00 2001 From: Nick Van Rymenant Date: Tue, 25 Apr 2023 12:43:02 +0200 Subject: [PATCH 131/153] chore(adopters): adding klarrio --- ADOPTERS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 732d047a..c2eaf762 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -10,6 +10,9 @@ This is a list of companies that have adopted Capsule, feel free to open a Pull- ### [Fastweb](https://www.fastweb.it/) ![Fastweb](https://www.fastweb.it/grandi-aziende/gfx/common/logo-fastweb-header.svg) +### [Klarrio](https://klarrio.com/) +![Klarrio](https://klarrio.com/wp-content/uploads/klarrio.png) + ### [Politecnico di Torino](https://www.polito.it/) ![Politecnico di Torino](https://www.polito.it/themes/custom/polito/logo.svg) From 5977bbd9e11fba4c5f947c83912208c291e0b642 Mon Sep 17 00:00:00 2001 From: pheianox <77569421+pheianox@users.noreply.github.com> Date: Thu, 4 May 2023 20:47:12 +0400 Subject: [PATCH 132/153] chore(adopters): add PITS Global Data Recovery Services --- ADOPTERS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index c2eaf762..3b322a88 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -13,6 +13,9 @@ This is a list of companies that have adopted Capsule, feel free to open a Pull- ### [Klarrio](https://klarrio.com/) ![Klarrio](https://klarrio.com/wp-content/uploads/klarrio.png) +### [PITS Global Data Recovery Services](https://www.pitsdatarecovery.net) +![PITS Global Data Recovery Services](https://www.pitsdatarecovery.net/wp-content/uploads/2020/09/pits-logo.svg) + ### [Politecnico di Torino](https://www.polito.it/) ![Politecnico di Torino](https://www.polito.it/themes/custom/polito/logo.svg) From 29fed1d7367a3cc61aee82eead7cb58bbb90280c Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 11 May 2023 10:48:51 +0200 Subject: [PATCH 133/153] chore(github): bumping up v1.26 kindest/node --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 910d2f14..27977ec6 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - k8s-version: ['v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.1'] + k8s-version: ['v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.3'] runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 From 6c5399af3048cdb5c502d0b772d1fa44f2d268ea Mon Sep 17 00:00:00 2001 From: slimm609 Date: Mon, 15 May 2023 08:05:50 -0400 Subject: [PATCH 134/153] chore(gh): update build to golang 1.19 --- .github/workflows/ci.yml | 2 +- .github/workflows/e2e.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 216e09b4..cc15c588 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-go@v2 with: - go-version: '1.18' + go-version: '1.19' - run: make installer - name: Checking if YAML installer file is not aligned run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 27977ec6..c49b9b6e 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -40,7 +40,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-go@v2 with: - go-version: '1.18' + go-version: '1.19' - run: make manifests - name: Checking if manifests are disaligned run: test -z "$(git diff 2> /dev/null)" From 77f7061c7322808664c2fc0f1f839eeac44a3cc3 Mon Sep 17 00:00:00 2001 From: slimm609 Date: Mon, 15 May 2023 08:06:40 -0400 Subject: [PATCH 135/153] chore(build): update to golang 1.19 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7dcab99c..f08a6738 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.18 as builder +FROM golang:1.19 as builder WORKDIR /workspace # Copy the Go Modules manifests From f1fe45ef8e219fcede22425d7e31a57360d742cb Mon Sep 17 00:00:00 2001 From: slimm609 Date: Mon, 15 May 2023 08:07:24 -0400 Subject: [PATCH 136/153] chore(deps): update go.mod deps to golang 1.19 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 205ee39b..ab7c0169 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/clastix/capsule -go 1.18 +go 1.19 require ( github.com/go-logr/logr v1.2.0 From 9d06f687d22c1746f51ca6f77b921311c980d1df Mon Sep 17 00:00:00 2001 From: slimm609 Date: Mon, 15 May 2023 08:08:01 -0400 Subject: [PATCH 137/153] docs: update docs to use golang 1.19 --- docs/content/contributing/development.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/contributing/development.md b/docs/content/contributing/development.md index f002ae97..c0a9c926 100644 --- a/docs/content/contributing/development.md +++ b/docs/content/contributing/development.md @@ -4,7 +4,7 @@ Make sure you have these tools installed: -- [Go 1.18+](https://golang.org/dl/) +- [Go 1.19+](https://golang.org/dl/) - [Operator SDK 1.7.2+](https://github.com/operator-framework/operator-sdk), or [Kubebuilder](https://github.com/kubernetes-sigs/kubebuilder) - [KinD](https://github.com/kubernetes-sigs/kind) or [k3d](https://k3d.io/), with `kubectl` - [ngrok](https://ngrok.com/) (if you want to run locally with remote Kubernetes) From 7abeb71ad65a13ae2d33f473529b44115b792374 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Wed, 24 May 2023 17:44:57 +0200 Subject: [PATCH 138/153] chore(gh): adding k8s 1.27 to the test matrix --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index c49b9b6e..61840464 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - k8s-version: ['v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.3'] + k8s-version: ['v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.3', 'v1.27.2'] runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 From 5457df7e96ddad9c025b211e8b07b3db2df07372 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Wed, 24 May 2023 17:31:03 +0200 Subject: [PATCH 139/153] refactor(deps): updating to controller-runtime 0.15.0 --- controllers/config/manager.go | 14 +- controllers/rbac/manager.go | 12 +- controllers/resources/global.go | 7 +- controllers/servicelabels/abstract.go | 6 - controllers/servicelabels/endpoint.go | 5 +- controllers/servicelabels/endpoint_slices.go | 3 +- controllers/servicelabels/service.go | 5 +- controllers/tenant/namespaces.go | 4 +- controllers/tls/manager.go | 9 +- e2e/suite_test.go | 4 +- go.mod | 99 +- go.sum | 894 +++---------------- main.go | 40 +- pkg/webhook/router.go | 14 +- pkg/webhook/tenant/custom_resource_quota.go | 12 +- 15 files changed, 223 insertions(+), 905 deletions(-) diff --git a/controllers/config/manager.go b/controllers/config/manager.go index 09f6a0e2..129733d6 100644 --- a/controllers/config/manager.go +++ b/controllers/config/manager.go @@ -18,18 +18,14 @@ import ( ) type Manager struct { - Log logr.Logger - Client client.Client -} - -// InjectClient injects the Client interface, required by the Runnable interface. -func (c *Manager) InjectClient(client client.Client) error { - c.Client = client + client client.Client - return nil + Log logr.Logger } func (c *Manager) SetupWithManager(mgr ctrl.Manager, configurationName string) error { + c.client = mgr.GetClient() + return ctrl.NewControllerManagedBy(mgr). For(&capsulev1beta2.CapsuleConfiguration{}, utils.NamesMatchingPredicate(configurationName)). Complete(c) @@ -38,7 +34,7 @@ func (c *Manager) SetupWithManager(mgr ctrl.Manager, configurationName string) e func (c *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res reconcile.Result, err error) { c.Log.Info("CapsuleConfiguration reconciliation started", "request.name", request.Name) - cfg := configuration.NewCapsuleConfiguration(ctx, c.Client, request.Name) + cfg := configuration.NewCapsuleConfiguration(ctx, c.client, request.Name) // Validating the Capsule Configuration options if _, err = cfg.ProtectedNamespaceRegexp(); err != nil { panic(errors.Wrap(err, "Invalid configuration for protected Namespace regex")) diff --git a/controllers/rbac/manager.go b/controllers/rbac/manager.go index f4470007..a5a8c086 100644 --- a/controllers/rbac/manager.go +++ b/controllers/rbac/manager.go @@ -19,7 +19,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/controllers/utils" @@ -32,13 +31,6 @@ type Manager struct { Configuration configuration.Configuration } -// InjectClient injects the Client interface, required by the Runnable interface. -func (r *Manager) InjectClient(c client.Client) error { - r.Client = c - - return nil -} - func (r *Manager) SetupWithManager(ctx context.Context, mgr ctrl.Manager, configurationName string) (err error) { namesPredicate := utils.NamesMatchingPredicate(ProvisionerRoleName, DeleterRoleName) @@ -51,8 +43,8 @@ func (r *Manager) SetupWithManager(ctx context.Context, mgr ctrl.Manager, config crbErr := ctrl.NewControllerManagedBy(mgr). For(&rbacv1.ClusterRoleBinding{}, namesPredicate). - Watches(source.NewKindWithCache(&capsulev1beta2.CapsuleConfiguration{}, mgr.GetCache()), handler.Funcs{ - UpdateFunc: func(updateEvent event.UpdateEvent, limitingInterface workqueue.RateLimitingInterface) { + Watches(&capsulev1beta2.CapsuleConfiguration{}, handler.Funcs{ + UpdateFunc: func(ctx context.Context, updateEvent event.UpdateEvent, limitingInterface workqueue.RateLimitingInterface) { if updateEvent.ObjectNew.GetName() == configurationName { if crbErr := r.EnsureClusterRoleBindings(ctx); crbErr != nil { r.Log.Error(err, "cannot update ClusterRoleBinding upon CapsuleConfiguration update") diff --git a/controllers/resources/global.go b/controllers/resources/global.go index 7cdb8f4d..70725720 100644 --- a/controllers/resources/global.go +++ b/controllers/resources/global.go @@ -20,7 +20,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" ) @@ -30,11 +29,11 @@ type Global struct { processor Processor } -func (r *Global) enqueueRequestFromTenant(object client.Object) (reqs []reconcile.Request) { +func (r *Global) enqueueRequestFromTenant(ctx context.Context, object client.Object) (reqs []reconcile.Request) { tnt := object.(*capsulev1beta2.Tenant) //nolint:forcetypeassert resList := capsulev1beta2.GlobalTenantResourceList{} - if err := r.client.List(context.Background(), &resList); err != nil { + if err := r.client.List(ctx, &resList); err != nil { return nil } @@ -70,7 +69,7 @@ func (r *Global) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&capsulev1beta2.GlobalTenantResource{}). - Watches(&source.Kind{Type: &capsulev1beta2.Tenant{}}, handler.EnqueueRequestsFromMapFunc(r.enqueueRequestFromTenant)). + Watches(&capsulev1beta2.Tenant{}, handler.EnqueueRequestsFromMapFunc(r.enqueueRequestFromTenant)). Complete(r) } diff --git a/controllers/servicelabels/abstract.go b/controllers/servicelabels/abstract.go index c0a8c189..19674a01 100644 --- a/controllers/servicelabels/abstract.go +++ b/controllers/servicelabels/abstract.go @@ -30,12 +30,6 @@ type abstractServiceLabelsReconciler struct { log logr.Logger } -func (r *abstractServiceLabelsReconciler) InjectClient(c client.Client) error { - r.client = c - - return nil -} - func (r *abstractServiceLabelsReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { tenant, err := r.getTenant(ctx, request.NamespacedName, r.client) if err != nil { diff --git a/controllers/servicelabels/endpoint.go b/controllers/servicelabels/endpoint.go index d9eb80bc..26b0d182 100644 --- a/controllers/servicelabels/endpoint.go +++ b/controllers/servicelabels/endpoint.go @@ -19,8 +19,9 @@ type EndpointsLabelsReconciler struct { func (r *EndpointsLabelsReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{ - obj: &corev1.Endpoints{}, - log: r.Log, + obj: &corev1.Endpoints{}, + client: mgr.GetClient(), + log: r.Log, } return ctrl.NewControllerManagedBy(mgr). diff --git a/controllers/servicelabels/endpoint_slices.go b/controllers/servicelabels/endpoint_slices.go index 81f098e9..efcee1f7 100644 --- a/controllers/servicelabels/endpoint_slices.go +++ b/controllers/servicelabels/endpoint_slices.go @@ -22,7 +22,8 @@ type EndpointSlicesLabelsReconciler struct { func (r *EndpointSlicesLabelsReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{ - log: r.Log, + client: mgr.GetClient(), + log: r.Log, } switch { diff --git a/controllers/servicelabels/service.go b/controllers/servicelabels/service.go index 9c28d5b1..505f00f3 100644 --- a/controllers/servicelabels/service.go +++ b/controllers/servicelabels/service.go @@ -19,8 +19,9 @@ type ServicesLabelsReconciler struct { func (r *ServicesLabelsReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { r.abstractServiceLabelsReconciler = abstractServiceLabelsReconciler{ - obj: &corev1.Service{}, - log: r.Log, + obj: &corev1.Service{}, + client: mgr.GetClient(), + log: r.Log, } return ctrl.NewControllerManagedBy(mgr). diff --git a/controllers/tenant/namespaces.go b/controllers/tenant/namespaces.go index 5ff976a4..3262511c 100644 --- a/controllers/tenant/namespaces.go +++ b/controllers/tenant/namespaces.go @@ -158,7 +158,7 @@ func (r *Manager) ensureNamespaceCount(ctx context.Context, tenant *capsulev1bet found.Status.Size = tenant.Status.Size - return r.Client.Status().Update(ctx, found, &client.UpdateOptions{}) + return r.Client.Status().Update(ctx, found, &client.SubResourceUpdateOptions{}) }) } @@ -176,7 +176,7 @@ func (r *Manager) collectNamespaces(ctx context.Context, tenant *capsulev1beta2. _, err = controllerutil.CreateOrUpdate(ctx, r.Client, tenant.DeepCopy(), func() error { tenant.AssignNamespaces(list.Items) - return r.Client.Status().Update(ctx, tenant, &client.UpdateOptions{}) + return r.Client.Status().Update(ctx, tenant, &client.SubResourceUpdateOptions{}) }) return diff --git a/controllers/tls/manager.go b/controllers/tls/manager.go index d4566bbb..9a043f1c 100644 --- a/controllers/tls/manager.go +++ b/controllers/tls/manager.go @@ -27,7 +27,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" "github.com/clastix/capsule/controllers/utils" "github.com/clastix/capsule/pkg/cert" @@ -49,7 +48,7 @@ type Reconciler struct { } func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { - enqueueFn := handler.EnqueueRequestsFromMapFunc(func(client.Object) []reconcile.Request { + enqueueFn := handler.EnqueueRequestsFromMapFunc(func(context.Context, client.Object) []reconcile.Request { return []reconcile.Request{ { NamespacedName: types.NamespacedName{ @@ -62,13 +61,13 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&corev1.Secret{}, utils.NamesMatchingPredicate(r.Configuration.TLSSecretName())). - Watches(source.NewKindWithCache(&admissionregistrationv1.ValidatingWebhookConfiguration{}, mgr.GetCache()), enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { + Watches(&admissionregistrationv1.ValidatingWebhookConfiguration{}, enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { return object.GetName() == r.Configuration.ValidatingWebhookConfigurationName() }))). - Watches(source.NewKindWithCache(&admissionregistrationv1.MutatingWebhookConfiguration{}, mgr.GetCache()), enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { + Watches(&admissionregistrationv1.MutatingWebhookConfiguration{}, enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { return object.GetName() == r.Configuration.MutatingWebhookConfigurationName() }))). - Watches(source.NewKindWithCache(&apiextensionsv1.CustomResourceDefinition{}, mgr.GetCache()), enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { + Watches(&apiextensionsv1.CustomResourceDefinition{}, enqueueFn, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { return object.GetName() == r.Configuration.TenantCRDName() }))). Complete(r) diff --git a/e2e/suite_test.go b/e2e/suite_test.go index 0f890d18..b832327e 100644 --- a/e2e/suite_test.go +++ b/e2e/suite_test.go @@ -9,6 +9,7 @@ import ( "testing" . "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/reporters" . "github.com/onsi/gomega" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" @@ -16,7 +17,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/envtest" - "sigs.k8s.io/controller-runtime/pkg/envtest/printer" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -38,7 +38,7 @@ func TestAPIs(t *testing.T) { RunSpecsWithDefaultAndCustomReporters(t, "Controller Suite", - []Reporter{printer.NewlineReporter{}}) + []Reporter{&reporters.JUnitReporter{}}) } var _ = BeforeSuite(func(done Done) { diff --git a/go.mod b/go.mod index ab7c0169..a01f5ad3 100644 --- a/go.mod +++ b/go.mod @@ -3,83 +3,80 @@ module github.com/clastix/capsule go 1.19 require ( - github.com/go-logr/logr v1.2.0 + github.com/go-logr/logr v1.2.4 github.com/hashicorp/go-multierror v1.1.0 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.18.1 + github.com/onsi/gomega v1.27.7 github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.8.1 github.com/valyala/fasttemplate v1.2.2 - go.uber.org/zap v1.19.1 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - k8s.io/api v0.24.2 - k8s.io/apiextensions-apiserver v0.24.2 - k8s.io/apimachinery v0.24.2 - k8s.io/client-go v0.24.2 - k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 - sigs.k8s.io/cluster-api v1.2.4 - sigs.k8s.io/controller-runtime v0.12.3 + go.uber.org/zap v1.24.0 + golang.org/x/sync v0.1.0 + k8s.io/api v0.27.2 + k8s.io/apiextensions-apiserver v0.27.2 + k8s.io/apimachinery v0.27.2 + k8s.io/client-go v0.27.2 + k8s.io/utils v0.0.0-20230209194617-a36077c30491 + sigs.k8s.io/cluster-api v1.4.0-beta.2.0.20230524193452-89a36acc3c3f + sigs.k8s.io/controller-runtime v0.15.0 ) require ( - cloud.google.com/go v0.81.0 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful v2.16.0+incompatible // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/go-logr/zapr v1.2.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect - github.com/go-openapi/swag v0.19.14 // indirect - github.com/gobuffalo/flect v0.2.5 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/zapr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.1 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gobuffalo/flect v1.0.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.8 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.6 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect + github.com/prometheus/client_golang v1.15.1 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect - gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/time v0.3.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0 // indirect - k8s.io/component-base v0.24.2 // indirect - k8s.io/klog/v2 v2.60.1 // indirect - k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect - sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/component-base v0.27.2 // indirect + k8s.io/klog/v2 v2.90.1 // indirect + k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index db757e6b..283b0062 100644 --- a/go.sum +++ b/go.sum @@ -1,213 +1,80 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e h1:GCzyKMDDjSGnlpl3clrdAK7I1AaVoaiKDOYkUzChZzg= -github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0= -github.com/coredns/corefile-migration v1.0.17 h1:tNwh8+4WOANV6NjSljwgW7qViJfhvPUt1kosj4rR8yg= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/coredns/corefile-migration v1.0.20 h1:MdOkT6F3ehju/n9tgxlGct8XAajOX2vN+wG7To4BWSI= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.16.0+incompatible h1:rgqiKNjTnFQA6kkhFe16D8epTksy9HQ1MyrbDXSdYhM= -github.com/emicklei/go-restful v2.16.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= -github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= +github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobuffalo/flect v0.2.5 h1:H6vvsv2an0lalEaCDRThvtBfmg44W/QHXBCYUXf/6S4= -github.com/gobuffalo/flect v0.2.5/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= +github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -217,707 +84,236 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.10.1 h1:MQBGSZGnDwh7T/un+mzGKOMz3x+4E/GDPprWjDL+1Jg= -github.com/google/cel-go v0.10.1/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= -github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= -go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= -go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= -go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= -go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8= -go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb h1:8tDJ3aechhddbdPAxpycgXHJRMLpk/Ab+aa4OgdN5/g= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= +gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -927,35 +323,25 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -963,56 +349,36 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -inet.af/netaddr v0.0.0-20220617031823-097006376321 h1:B4dC8ySKTQXasnjDTMsoCMf1sQG4WsMej0WXaHxunmU= -k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI= -k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= -k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k= -k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ= -k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM= -k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/apiserver v0.24.2 h1:orxipm5elPJSkkFNlwH9ClqaKEDJJA3yR2cAAlCnyj4= -k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI= -k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA= -k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= -k8s.io/cluster-bootstrap v0.24.0 h1:MTs2x3Vfcl/PWvB5bfX7gzTFRyi4ZSbNSQgGJTCb6Sw= -k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= -k8s.io/component-base v0.24.2 h1:kwpQdoSfbcH+8MPN4tALtajLDfSfYxBDYlXobNWI6OU= -k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= -k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= -k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= -sigs.k8s.io/cluster-api v1.2.4 h1:wxfm/p8y+Q3qWVkkIPAIVqabA5lJVvqoRA02Nhup3uk= -sigs.k8s.io/cluster-api v1.2.4/go.mod h1:YaLJOC9mSsIOpdbh7BpthGmC8uxIJADzrMMIGpgahfM= -sigs.k8s.io/controller-runtime v0.12.3 h1:FCM8xeY/FI8hoAfh/V4XbbYMY20gElh9yh+A98usMio= -sigs.k8s.io/controller-runtime v0.12.3/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0= -sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= -sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= +k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= +k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo= +k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ= +k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= +k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +k8s.io/apiserver v0.27.2 h1:p+tjwrcQEZDrEorCZV2/qE8osGTINPuS5ZNqWAvKm5E= +k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= +k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= +k8s.io/cluster-bootstrap v0.27.2 h1:OL3onrOwrUD7NQxBUqQwTl1Uu2GQKCkw9BMHpc4PbiA= +k8s.io/component-base v0.27.2 h1:neju+7s/r5O4x4/txeUONNTS9r1HsPbyoPBAtHsDCpo= +k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo= +k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= +k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= +k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/cluster-api v1.4.0-beta.2.0.20230524193452-89a36acc3c3f h1:mqGSAkdrKHfeUB4A4mD55NfHXfQe+FqaB5tUFSCSptY= +sigs.k8s.io/cluster-api v1.4.0-beta.2.0.20230524193452-89a36acc3c3f/go.mod h1:VgMs4bjc3P0igCtHAbG9+jix2gyYtECQhKfNfRedStc= +sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= +sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/main.go b/main.go index 4e989998..52d28240 100644 --- a/main.go +++ b/main.go @@ -21,10 +21,10 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + ctrlwebhook "sigs.k8s.io/controller-runtime/pkg/webhook" capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1" capsulev1beta1 "github.com/clastix/capsule/api/v1beta1" @@ -76,27 +76,6 @@ func printVersion() { setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH)) } -func newDelegatingClient(cache cache.Cache, config *rest.Config, options client.Options, uncachedObjects ...client.Object) (client.Client, error) { - cl, err := client.New(config, options) - if err != nil { - return nil, err - } - - delegatingClient, err := client.NewDelegatingClient( - client.NewDelegatingClientInput{ - Client: cl, - CacheReader: cache, - UncachedObjects: uncachedObjects, - CacheUnstructured: true, - }, - ) - if err != nil { - return nil, err - } - - return delegatingClient, nil -} - //nolint:maintidx,cyclop func main() { var enableLeaderElection, version bool @@ -144,13 +123,19 @@ func main() { } manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - MetricsBindAddress: metricsAddr, - Port: webhookPort, + Scheme: scheme, + MetricsBindAddress: metricsAddr, + WebhookServer: ctrlwebhook.NewServer(ctrlwebhook.Options{ + Port: webhookPort, + }), LeaderElection: enableLeaderElection, LeaderElectionID: "42c733ea.clastix.capsule.io", HealthProbeBindAddress: ":10080", - NewClient: newDelegatingClient, + NewClient: func(config *rest.Config, options client.Options) (client.Client, error) { + options.Cache.Unstructured = true + + return client.New(config, options) + }, }) if err != nil { setupLog.Error(err, "unable to start manager") @@ -250,7 +235,7 @@ func main() { route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())), route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler(), tenant.ForbiddenAnnotationsRegexHandler(), tenant.ProtectedHandler()), route.OwnerReference(utils.InCapsuleGroups(cfg, namespacewebhook.OwnerReferenceHandler(), ownerreference.Handler(cfg))), - route.Cordoning(tenant.CordoningHandler(cfg), tenant.ResourceCounterHandler()), + route.Cordoning(tenant.CordoningHandler(cfg), tenant.ResourceCounterHandler(manager.GetClient())), route.Node(utils.InCapsuleGroups(cfg, node.UserMetadataHandler(cfg, kubeVersion))), route.Defaults(defaults.Handler(cfg, kubeVersion)), ) @@ -267,6 +252,7 @@ func main() { rbacManager := &rbaccontroller.Manager{ Log: ctrl.Log.WithName("controllers").WithName("Rbac"), + Client: manager.GetClient(), Configuration: cfg, } diff --git a/pkg/webhook/router.go b/pkg/webhook/router.go index 3f2ab521..3c765fa1 100644 --- a/pkg/webhook/router.go +++ b/pkg/webhook/router.go @@ -22,6 +22,8 @@ func Register(manager controllerruntime.Manager, webhookList ...Webhook) error { for _, wh := range webhookList { server.Register(wh.GetPath(), &webhook.Admission{ Handler: &handlerRouter{ + client: manager.GetClient(), + decoder: admission.NewDecoder(manager.GetScheme()), recorder: recorder, handlers: wh.GetHandlers(), }, @@ -65,15 +67,3 @@ func (r *handlerRouter) Handle(ctx context.Context, req admission.Request) admis return admission.Allowed("") } - -func (r *handlerRouter) InjectClient(c client.Client) error { - r.client = c - - return nil -} - -func (r *handlerRouter) InjectDecoder(d *admission.Decoder) error { - r.decoder = d - - return nil -} diff --git a/pkg/webhook/tenant/custom_resource_quota.go b/pkg/webhook/tenant/custom_resource_quota.go index d8cbd7cc..e18ccbb9 100644 --- a/pkg/webhook/tenant/custom_resource_quota.go +++ b/pkg/webhook/tenant/custom_resource_quota.go @@ -25,14 +25,10 @@ type resourceCounterHandler struct { client client.Client } -func (r *resourceCounterHandler) InjectClient(c client.Client) error { - r.client = c - - return nil -} - -func ResourceCounterHandler() capsulewebhook.Handler { - return &resourceCounterHandler{} +func ResourceCounterHandler(client client.Client) capsulewebhook.Handler { + return &resourceCounterHandler{ + client: client, + } } func (r *resourceCounterHandler) getTenantName(ctx context.Context, clt client.Client, req admission.Request) (string, error) { From 809fa1174102872ffa99ddeb9aafe585441980d9 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 25 May 2023 09:53:08 +0200 Subject: [PATCH 140/153] refactor(golint): removing deprecated functions --- api/v1alpha1/conversion_hub.go | 6 +- api/v1alpha1/conversion_hub_test.go | 6 +- api/v1beta2/tenantresource_global.go | 5 +- .../crd/bases/capsule.clastix.io_tenants.yaml | 842 +++++++++--------- controllers/resources/global.go | 2 +- controllers/resources/namespaced.go | 2 +- controllers/resources/processor.go | 2 +- controllers/tls/manager.go | 4 +- e2e/disable_externalname_test.go | 2 +- e2e/disable_loadbalancer_test.go | 2 +- e2e/disable_node_ports_test.go | 2 +- e2e/enable_loadbalancer_test.go | 2 +- e2e/ingress_class_extensions_test.go | 2 +- e2e/ingress_class_networking_test.go | 2 +- e2e/overquota_namespace_test.go | 2 +- e2e/resource_quota_exceeded_test.go | 3 +- e2e/service_metadata_test.go | 8 +- e2e/storage_class_test.go | 2 +- pkg/indexer/ingress/hostname_path.go | 2 +- pkg/indexer/ingress/utils.go | 18 +- pkg/webhook/ingress/types.go | 20 +- pkg/webhook/ingress/validate_hostnames.go | 8 +- 22 files changed, 472 insertions(+), 472 deletions(-) diff --git a/api/v1alpha1/conversion_hub.go b/api/v1alpha1/conversion_hub.go index e482ebb7..1332ff07 100644 --- a/api/v1alpha1/conversion_hub.go +++ b/api/v1alpha1/conversion_hub.go @@ -294,7 +294,7 @@ func (in *Tenant) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{} } - dst.Spec.ServiceOptions.AllowedServices.NodePort = pointer.BoolPtr(val) + dst.Spec.ServiceOptions.AllowedServices.NodePort = pointer.Bool(val) } enableExternalName, ok := annotations[enableExternalNameAnnotation] @@ -312,7 +312,7 @@ func (in *Tenant) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{} } - dst.Spec.ServiceOptions.AllowedServices.ExternalName = pointer.BoolPtr(val) + dst.Spec.ServiceOptions.AllowedServices.ExternalName = pointer.Bool(val) } loadBalancerService, ok := annotations[enableLoadBalancerAnnotation] @@ -330,7 +330,7 @@ func (in *Tenant) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{} } - dst.Spec.ServiceOptions.AllowedServices.LoadBalancer = pointer.BoolPtr(val) + dst.Spec.ServiceOptions.AllowedServices.LoadBalancer = pointer.Bool(val) } // Status dst.Status = capsulev1beta1.TenantStatus{ diff --git a/api/v1alpha1/conversion_hub_test.go b/api/v1alpha1/conversion_hub_test.go index 21c512dc..64a2e369 100644 --- a/api/v1alpha1/conversion_hub_test.go +++ b/api/v1alpha1/conversion_hub_test.go @@ -53,9 +53,9 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) { v1beta1ServiceOptions := &api.ServiceOptions{ AdditionalMetadata: v1beta1AdditionalMetadataSpec, AllowedServices: &api.AllowedServices{ - NodePort: pointer.BoolPtr(false), - ExternalName: pointer.BoolPtr(false), - LoadBalancer: pointer.BoolPtr(false), + NodePort: pointer.Bool(false), + ExternalName: pointer.Bool(false), + LoadBalancer: pointer.Bool(false), }, ExternalServiceIPs: &api.ExternalServiceIPsSpec{ Allowed: []api.AllowedIP{"192.168.0.1"}, diff --git a/api/v1beta2/tenantresource_global.go b/api/v1beta2/tenantresource_global.go index 1feced99..9108e533 100644 --- a/api/v1beta2/tenantresource_global.go +++ b/api/v1beta2/tenantresource_global.go @@ -25,8 +25,9 @@ type GlobalTenantResourceStatus struct { type ProcessedItems []ObjectReferenceStatus -func (p *ProcessedItems) AsSet() sets.String { - set := sets.NewString() +func (p *ProcessedItems) AsSet() sets.Set[string] { + set := sets.New[string]() + for _, i := range *p { set.Insert(i.String()) } diff --git a/config/crd/bases/capsule.clastix.io_tenants.yaml b/config/crd/bases/capsule.clastix.io_tenants.yaml index 6f0977c0..75c54723 100644 --- a/config/crd/bases/capsule.clastix.io_tenants.yaml +++ b/config/crd/bases/capsule.clastix.io_tenants.yaml @@ -245,15 +245,15 @@ spec: description: NetworkPolicySpec provides the specification of a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected - pods. Outgoing traffic is allowed if there are no NetworkPolicies - selecting the pod (and cluster policy otherwise allows the - traffic), OR if the traffic matches at least one egress rule - across all of the NetworkPolicy objects whose podSelector - matches the pod. If this field is empty then this NetworkPolicy - limits all outgoing traffic (and serves solely to ensure that - the pods it selects are isolated by default). This field is - beta-level in 1.8 + description: egress is a list of egress rules to be applied + to the selected pods. Outgoing traffic is allowed if there + are no NetworkPolicies selecting the pod (and cluster policy + otherwise allows the traffic), OR if the traffic matches at + least one egress rule across all of the NetworkPolicy objects + whose podSelector matches the pod. If this field is empty + then this NetworkPolicy limits all outgoing traffic (and serves + solely to ensure that the pods it selects are isolated by + default). This field is beta-level in 1.8 items: description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a @@ -261,52 +261,51 @@ spec: both ports and to. This type is beta-level in 1.8 properties: ports: - description: List of destination ports for outgoing traffic. - Each item in this list is combined using a logical OR. - If this field is empty or missing, this rule matches - all ports (traffic not restricted by port). If this - field is present and contains at least one item, then - this rule allows traffic only if the traffic matches - at least one port in the list. + description: ports is a list of destination ports for + outgoing traffic. Each item in this list is combined + using a logical OR. If this field is empty or missing, + this rule matches all ports (traffic not restricted + by port). If this field is present and contains at least + one item, then this rule allows traffic only if the + traffic matches at least one port in the list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range of - ports from port to endPort, inclusive, should - be allowed by the policy. This field cannot be - defined if the port field is not defined or if - the port field is defined as a named (string) + description: endPort indicates that the range of + ports from port to endPort if set, inclusive, + should be allowed by the policy. This field cannot + be defined if the port field is not defined or + if the port field is defined as a named (string) port. The endPort must be equal or greater than - port. This feature is in Beta state and is enabled - by default. It can be disabled using the Feature - Gate "NetworkPolicyEndPort". + port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This - can either be a numerical or named port on a pod. - If this field is not provided, this matches all - port names and numbers. If present, only traffic - on the specified protocol AND port will be matched. + description: port represents the port on the given + protocol. This can either be a numerical or named + port on a pod. If this field is not provided, + this matches all port names and numbers. If present, + only traffic on the specified protocol AND port + will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which - traffic must match. If not specified, this field - defaults to TCP. + description: protocol represents the protocol (TCP, + UDP, or SCTP) which traffic must match. If not + specified, this field defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic - of pods selected for this rule. Items in this list are - combined using a logical OR operation. If this field - is empty or missing, this rule matches all destinations + description: to is a list of destinations for outgoing + traffic of pods selected for this rule. Items in this + list are combined using a logical OR operation. If this + field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least @@ -317,21 +316,21 @@ spec: are allowed properties: ipBlock: - description: IPBlock defines policy on a particular + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing the - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" + description: cidr is a string representing the + IPBlock Valid examples are "192.168.1.0/24" + or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs that - should not be included within an IP Block - Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: except is a slice of CIDRs that + should not be included within an IPBlock Valid + examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are - outside the CIDR range + outside the cidr range items: type: string type: array @@ -339,14 +338,14 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label selector - semantics; if present but empty, it selects all - namespaces. \n If PodSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects all - Pods in the Namespaces selected by NamespaceSelector." + description: "namespaceSelector selects namespaces + using cluster-scoped labels. This field follows + standard label selector semantics; if present + but empty, it selects all namespaces. \n If podSelector + is also set, then the NetworkPolicyPeer as a whole + selects the pods matching podSelector in the namespaces + selected by namespaceSelector. Otherwise it selects + all pods in the namespaces selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list of label @@ -397,15 +396,15 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects - Pods. This field follows standard label selector - semantics; if present but empty, it selects all - pods. \n If NamespaceSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects the - Pods matching PodSelector in the policy's own - Namespace." + description: "podSelector is a label selector which + selects pods. This field follows standard label + selector semantics; if present but empty, it selects + all pods. \n If namespaceSelector is also set, + then the NetworkPolicyPeer as a whole selects + the pods matching podSelector in the Namespaces + selected by NamespaceSelector. Otherwise it selects + the pods matching podSelector in the policy's + own namespace." properties: matchExpressions: description: matchExpressions is a list of label @@ -460,15 +459,15 @@ spec: type: object type: array ingress: - description: List of ingress rules to be applied to the selected - pods. Traffic is allowed to a pod if there are no NetworkPolicies - selecting the pod (and cluster policy otherwise allows the - traffic), OR if the traffic source is the pod's local node, - OR if the traffic matches at least one ingress rule across - all of the NetworkPolicy objects whose podSelector matches - the pod. If this field is empty then this NetworkPolicy does - not allow any traffic (and serves solely to ensure that the - pods it selects are isolated by default) + description: ingress is a list of ingress rules to be applied + to the selected pods. Traffic is allowed to a pod if there + are no NetworkPolicies selecting the pod (and cluster policy + otherwise allows the traffic), OR if the traffic source is + the pod's local node, OR if the traffic matches at least one + ingress rule across all of the NetworkPolicy objects whose + podSelector matches the pod. If this field is empty then this + NetworkPolicy does not allow any traffic (and serves solely + to ensure that the pods it selects are isolated by default) items: description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a @@ -476,35 +475,35 @@ spec: both ports and from. properties: from: - description: List of sources which should be able to access - the pods selected for this rule. Items in this list - are combined using a logical OR operation. If this field - is empty or missing, this rule matches all sources (traffic - not restricted by source). If this field is present - and contains at least one item, this rule allows traffic - only if the traffic matches at least one item in the - from list. + description: from is a list of sources which should be + able to access the pods selected for this rule. Items + in this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all sources (traffic not restricted by source). If this + field is present and contains at least one item, this + rule allows traffic only if the traffic matches at least + one item in the from list. items: description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing the - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" + description: cidr is a string representing the + IPBlock Valid examples are "192.168.1.0/24" + or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs that - should not be included within an IP Block - Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: except is a slice of CIDRs that + should not be included within an IPBlock Valid + examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are - outside the CIDR range + outside the cidr range items: type: string type: array @@ -512,14 +511,14 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label selector - semantics; if present but empty, it selects all - namespaces. \n If PodSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects all - Pods in the Namespaces selected by NamespaceSelector." + description: "namespaceSelector selects namespaces + using cluster-scoped labels. This field follows + standard label selector semantics; if present + but empty, it selects all namespaces. \n If podSelector + is also set, then the NetworkPolicyPeer as a whole + selects the pods matching podSelector in the namespaces + selected by namespaceSelector. Otherwise it selects + all pods in the namespaces selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list of label @@ -570,15 +569,15 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects - Pods. This field follows standard label selector - semantics; if present but empty, it selects all - pods. \n If NamespaceSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects the - Pods matching PodSelector in the policy's own - Namespace." + description: "podSelector is a label selector which + selects pods. This field follows standard label + selector semantics; if present but empty, it selects + all pods. \n If namespaceSelector is also set, + then the NetworkPolicyPeer as a whole selects + the pods matching podSelector in the Namespaces + selected by NamespaceSelector. Otherwise it selects + the pods matching podSelector in the policy's + own namespace." properties: matchExpressions: description: matchExpressions is a list of label @@ -631,57 +630,56 @@ spec: type: object type: array ports: - description: List of ports which should be made accessible - on the pods selected for this rule. Each item in this - list is combined using a logical OR. If this field is - empty or missing, this rule matches all ports (traffic - not restricted by port). If this field is present and - contains at least one item, then this rule allows traffic - only if the traffic matches at least one port in the - list. + description: ports is a list of ports which should be + made accessible on the pods selected for this rule. + Each item in this list is combined using a logical OR. + If this field is empty or missing, this rule matches + all ports (traffic not restricted by port). If this + field is present and contains at least one item, then + this rule allows traffic only if the traffic matches + at least one port in the list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range of - ports from port to endPort, inclusive, should - be allowed by the policy. This field cannot be - defined if the port field is not defined or if - the port field is defined as a named (string) + description: endPort indicates that the range of + ports from port to endPort if set, inclusive, + should be allowed by the policy. This field cannot + be defined if the port field is not defined or + if the port field is defined as a named (string) port. The endPort must be equal or greater than - port. This feature is in Beta state and is enabled - by default. It can be disabled using the Feature - Gate "NetworkPolicyEndPort". + port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This - can either be a numerical or named port on a pod. - If this field is not provided, this matches all - port names and numbers. If present, only traffic - on the specified protocol AND port will be matched. + description: port represents the port on the given + protocol. This can either be a numerical or named + port on a pod. If this field is not provided, + this matches all port names and numbers. If present, + only traffic on the specified protocol AND port + will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which - traffic must match. If not specified, this field - defaults to TCP. + description: protocol represents the protocol (TCP, + UDP, or SCTP) which traffic must match. If not + specified, this field defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object - applies. The array of ingress rules is applied to any pods - selected by this field. Multiple network policies can select - the same set of pods. In this case, the ingress rules for - each are combined additively. This field is NOT optional and - follows standard label selector semantics. An empty podSelector + description: podSelector selects the pods to which this NetworkPolicy + object applies. The array of ingress rules is applied to any + pods selected by this field. Multiple network policies can + select the same set of pods. In this case, the ingress rules + for each are combined additively. This field is NOT optional + and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. properties: matchExpressions: @@ -727,18 +725,18 @@ spec: type: object x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates - to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", - "Egress"]. If this field is not specified, it will default - based on the existence of Ingress or Egress rules; policies - that contain an Egress section are assumed to affect Egress, - and all policies (whether or not they contain an Ingress section) - are assumed to affect Ingress. If you want to write an egress-only - policy, you must explicitly specify policyTypes [ "Egress" - ]. Likewise, if you want to write a policy that specifies - that no egress is allowed, you must specify a policyTypes + description: policyTypes is a list of rule types that the NetworkPolicy + relates to. Valid options are ["Ingress"], ["Egress"], or + ["Ingress", "Egress"]. If this field is not specified, it + will default based on the existence of ingress or egress rules; + policies that contain an egress section are assumed to affect + egress, and all policies (whether or not they contain an ingress + section) are assumed to affect ingress. If you want to write + an egress-only policy, you must explicitly specify policyTypes + [ "Egress" ]. Likewise, if you want to write a policy that + specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not - include an Egress section and would otherwise default to just + include an egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: description: PolicyType string describes the NetworkPolicy @@ -1156,15 +1154,16 @@ spec: a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected - pods. Outgoing traffic is allowed if there are no NetworkPolicies - selecting the pod (and cluster policy otherwise allows - the traffic), OR if the traffic matches at least one egress - rule across all of the NetworkPolicy objects whose podSelector - matches the pod. If this field is empty then this NetworkPolicy - limits all outgoing traffic (and serves solely to ensure - that the pods it selects are isolated by default). This - field is beta-level in 1.8 + description: egress is a list of egress rules to be applied + to the selected pods. Outgoing traffic is allowed if there + are no NetworkPolicies selecting the pod (and cluster + policy otherwise allows the traffic), OR if the traffic + matches at least one egress rule across all of the NetworkPolicy + objects whose podSelector matches the pod. If this field + is empty then this NetworkPolicy limits all outgoing traffic + (and serves solely to ensure that the pods it selects + are isolated by default). This field is beta-level in + 1.8 items: description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by @@ -1173,55 +1172,53 @@ spec: 1.8 properties: ports: - description: List of destination ports for outgoing - traffic. Each item in this list is combined using - a logical OR. If this field is empty or missing, - this rule matches all ports (traffic not restricted - by port). If this field is present and contains - at least one item, then this rule allows traffic - only if the traffic matches at least one port in - the list. + description: ports is a list of destination ports + for outgoing traffic. Each item in this list is + combined using a logical OR. If this field is empty + or missing, this rule matches all ports (traffic + not restricted by port). If this field is present + and contains at least one item, then this rule allows + traffic only if the traffic matches at least one + port in the list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range - of ports from port to endPort, inclusive, + description: endPort indicates that the range + of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be - equal or greater than port. This feature is - in Beta state and is enabled by default. It - can be disabled using the Feature Gate "NetworkPolicyEndPort". + equal or greater than port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. - This can either be a numerical or named port - on a pod. If this field is not provided, this - matches all port names and numbers. If present, - only traffic on the specified protocol AND - port will be matched. + description: port represents the port on the + given protocol. This can either be a numerical + or named port on a pod. If this field is not + provided, this matches all port names and + numbers. If present, only traffic on the specified + protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) - which traffic must match. If not specified, - this field defaults to TCP. + description: protocol represents the protocol + (TCP, UDP, or SCTP) which traffic must match. + If not specified, this field defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic - of pods selected for this rule. Items in this list - are combined using a logical OR operation. If this - field is empty or missing, this rule matches all - destinations (traffic not restricted by destination). + description: to is a list of destinations for outgoing + traffic of pods selected for this rule. Items in + this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. @@ -1231,21 +1228,21 @@ spec: of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing - the IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" + description: cidr is a string representing + the IPBlock Valid examples are "192.168.1.0/24" + or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs + description: except is a slice of CIDRs that should not be included within an - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" Except values will - be rejected if they are outside the CIDR + IPBlock Valid examples are "192.168.1.0/24" + or "2001:db8::/64" Except values will + be rejected if they are outside the cidr range items: type: string @@ -1254,15 +1251,15 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label - selector semantics; if present but empty, - it selects all namespaces. \n If PodSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects all Pods in the Namespaces - selected by NamespaceSelector." + description: "namespaceSelector selects namespaces + using cluster-scoped labels. This field follows + standard label selector semantics; if present + but empty, it selects all namespaces. \n If + podSelector is also set, then the NetworkPolicyPeer + as a whole selects the pods matching podSelector + in the namespaces selected by namespaceSelector. + Otherwise it selects all pods in the namespaces + selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list @@ -1314,15 +1311,15 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which - selects Pods. This field follows standard + description: "podSelector is a label selector + which selects pods. This field follows standard label selector semantics; if present but empty, - it selects all pods. \n If NamespaceSelector + it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector + a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. - Otherwise it selects the Pods matching PodSelector - in the policy's own Namespace." + Otherwise it selects the pods matching podSelector + in the policy's own namespace." properties: matchExpressions: description: matchExpressions is a list @@ -1378,12 +1375,12 @@ spec: type: object type: array ingress: - description: List of ingress rules to be applied to the - selected pods. Traffic is allowed to a pod if there are - no NetworkPolicies selecting the pod (and cluster policy - otherwise allows the traffic), OR if the traffic source - is the pod's local node, OR if the traffic matches at - least one ingress rule across all of the NetworkPolicy + description: ingress is a list of ingress rules to be applied + to the selected pods. Traffic is allowed to a pod if there + are no NetworkPolicies selecting the pod (and cluster + policy otherwise allows the traffic), OR if the traffic + source is the pod's local node, OR if the traffic matches + at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects @@ -1395,35 +1392,36 @@ spec: match both ports and from. properties: from: - description: List of sources which should be able - to access the pods selected for this rule. Items - in this list are combined using a logical OR operation. - If this field is empty or missing, this rule matches - all sources (traffic not restricted by source). - If this field is present and contains at least one - item, this rule allows traffic only if the traffic - matches at least one item in the from list. + description: from is a list of sources which should + be able to access the pods selected for this rule. + Items in this list are combined using a logical + OR operation. If this field is empty or missing, + this rule matches all sources (traffic not restricted + by source). If this field is present and contains + at least one item, this rule allows traffic only + if the traffic matches at least one item in the + from list. items: description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing - the IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" + description: cidr is a string representing + the IPBlock Valid examples are "192.168.1.0/24" + or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs + description: except is a slice of CIDRs that should not be included within an - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" Except values will - be rejected if they are outside the CIDR + IPBlock Valid examples are "192.168.1.0/24" + or "2001:db8::/64" Except values will + be rejected if they are outside the cidr range items: type: string @@ -1432,15 +1430,15 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label - selector semantics; if present but empty, - it selects all namespaces. \n If PodSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects all Pods in the Namespaces - selected by NamespaceSelector." + description: "namespaceSelector selects namespaces + using cluster-scoped labels. This field follows + standard label selector semantics; if present + but empty, it selects all namespaces. \n If + podSelector is also set, then the NetworkPolicyPeer + as a whole selects the pods matching podSelector + in the namespaces selected by namespaceSelector. + Otherwise it selects all pods in the namespaces + selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list @@ -1492,15 +1490,15 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which - selects Pods. This field follows standard + description: "podSelector is a label selector + which selects pods. This field follows standard label selector semantics; if present but empty, - it selects all pods. \n If NamespaceSelector + it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector + a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. - Otherwise it selects the Pods matching PodSelector - in the policy's own Namespace." + Otherwise it selects the pods matching podSelector + in the policy's own namespace." properties: matchExpressions: description: matchExpressions is a list @@ -1554,59 +1552,59 @@ spec: type: object type: array ports: - description: List of ports which should be made accessible - on the pods selected for this rule. Each item in - this list is combined using a logical OR. If this - field is empty or missing, this rule matches all - ports (traffic not restricted by port). If this - field is present and contains at least one item, - then this rule allows traffic only if the traffic - matches at least one port in the list. + description: ports is a list of ports which should + be made accessible on the pods selected for this + rule. Each item in this list is combined using a + logical OR. If this field is empty or missing, this + rule matches all ports (traffic not restricted by + port). If this field is present and contains at + least one item, then this rule allows traffic only + if the traffic matches at least one port in the + list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range - of ports from port to endPort, inclusive, + description: endPort indicates that the range + of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be - equal or greater than port. This feature is - in Beta state and is enabled by default. It - can be disabled using the Feature Gate "NetworkPolicyEndPort". + equal or greater than port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. - This can either be a numerical or named port - on a pod. If this field is not provided, this - matches all port names and numbers. If present, - only traffic on the specified protocol AND - port will be matched. + description: port represents the port on the + given protocol. This can either be a numerical + or named port on a pod. If this field is not + provided, this matches all port names and + numbers. If present, only traffic on the specified + protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) - which traffic must match. If not specified, - this field defaults to TCP. + description: protocol represents the protocol + (TCP, UDP, or SCTP) which traffic must match. + If not specified, this field defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy - object applies. The array of ingress rules is applied - to any pods selected by this field. Multiple network policies - can select the same set of pods. In this case, the ingress - rules for each are combined additively. This field is - NOT optional and follows standard label selector semantics. - An empty podSelector matches all pods in this namespace. + description: podSelector selects the pods to which this + NetworkPolicy object applies. The array of ingress rules + is applied to any pods selected by this field. Multiple + network policies can select the same set of pods. In this + case, the ingress rules for each are combined additively. + This field is NOT optional and follows standard label + selector semantics. An empty podSelector matches all pods + in this namespace. properties: matchExpressions: description: matchExpressions is a list of label selector @@ -1652,20 +1650,20 @@ spec: type: object x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates - to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", - "Egress"]. If this field is not specified, it will default - based on the existence of Ingress or Egress rules; policies - that contain an Egress section are assumed to affect Egress, - and all policies (whether or not they contain an Ingress - section) are assumed to affect Ingress. If you want to - write an egress-only policy, you must explicitly specify - policyTypes [ "Egress" ]. Likewise, if you want to write - a policy that specifies that no egress is allowed, you - must specify a policyTypes value that include "Egress" - (since such a policy would not include an Egress section - and would otherwise default to just [ "Ingress" ]). This - field is beta-level in 1.8 + description: policyTypes is a list of rule types that the + NetworkPolicy relates to. Valid options are ["Ingress"], + ["Egress"], or ["Ingress", "Egress"]. If this field is + not specified, it will default based on the existence + of ingress or egress rules; policies that contain an egress + section are assumed to affect egress, and all policies + (whether or not they contain an ingress section) are assumed + to affect ingress. If you want to write an egress-only + policy, you must explicitly specify policyTypes [ "Egress" + ]. Likewise, if you want to write a policy that specifies + that no egress is allowed, you must specify a policyTypes + value that include "Egress" (since such a policy would + not include an egress section and would otherwise default + to just [ "Ingress" ]). This field is beta-level in 1.8 items: description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 @@ -2277,15 +2275,16 @@ spec: a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected - pods. Outgoing traffic is allowed if there are no NetworkPolicies - selecting the pod (and cluster policy otherwise allows - the traffic), OR if the traffic matches at least one egress - rule across all of the NetworkPolicy objects whose podSelector - matches the pod. If this field is empty then this NetworkPolicy - limits all outgoing traffic (and serves solely to ensure - that the pods it selects are isolated by default). This - field is beta-level in 1.8 + description: egress is a list of egress rules to be applied + to the selected pods. Outgoing traffic is allowed if there + are no NetworkPolicies selecting the pod (and cluster + policy otherwise allows the traffic), OR if the traffic + matches at least one egress rule across all of the NetworkPolicy + objects whose podSelector matches the pod. If this field + is empty then this NetworkPolicy limits all outgoing traffic + (and serves solely to ensure that the pods it selects + are isolated by default). This field is beta-level in + 1.8 items: description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by @@ -2294,55 +2293,53 @@ spec: 1.8 properties: ports: - description: List of destination ports for outgoing - traffic. Each item in this list is combined using - a logical OR. If this field is empty or missing, - this rule matches all ports (traffic not restricted - by port). If this field is present and contains - at least one item, then this rule allows traffic - only if the traffic matches at least one port in - the list. + description: ports is a list of destination ports + for outgoing traffic. Each item in this list is + combined using a logical OR. If this field is empty + or missing, this rule matches all ports (traffic + not restricted by port). If this field is present + and contains at least one item, then this rule allows + traffic only if the traffic matches at least one + port in the list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range - of ports from port to endPort, inclusive, + description: endPort indicates that the range + of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be - equal or greater than port. This feature is - in Beta state and is enabled by default. It - can be disabled using the Feature Gate "NetworkPolicyEndPort". + equal or greater than port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. - This can either be a numerical or named port - on a pod. If this field is not provided, this - matches all port names and numbers. If present, - only traffic on the specified protocol AND - port will be matched. + description: port represents the port on the + given protocol. This can either be a numerical + or named port on a pod. If this field is not + provided, this matches all port names and + numbers. If present, only traffic on the specified + protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) - which traffic must match. If not specified, - this field defaults to TCP. + description: protocol represents the protocol + (TCP, UDP, or SCTP) which traffic must match. + If not specified, this field defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic - of pods selected for this rule. Items in this list - are combined using a logical OR operation. If this - field is empty or missing, this rule matches all - destinations (traffic not restricted by destination). + description: to is a list of destinations for outgoing + traffic of pods selected for this rule. Items in + this list are combined using a logical OR operation. + If this field is empty or missing, this rule matches + all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. @@ -2352,21 +2349,21 @@ spec: of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing - the IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" + description: cidr is a string representing + the IPBlock Valid examples are "192.168.1.0/24" + or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs + description: except is a slice of CIDRs that should not be included within an - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" Except values will - be rejected if they are outside the CIDR + IPBlock Valid examples are "192.168.1.0/24" + or "2001:db8::/64" Except values will + be rejected if they are outside the cidr range items: type: string @@ -2375,15 +2372,15 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label - selector semantics; if present but empty, - it selects all namespaces. \n If PodSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects all Pods in the Namespaces - selected by NamespaceSelector." + description: "namespaceSelector selects namespaces + using cluster-scoped labels. This field follows + standard label selector semantics; if present + but empty, it selects all namespaces. \n If + podSelector is also set, then the NetworkPolicyPeer + as a whole selects the pods matching podSelector + in the namespaces selected by namespaceSelector. + Otherwise it selects all pods in the namespaces + selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list @@ -2435,15 +2432,15 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which - selects Pods. This field follows standard + description: "podSelector is a label selector + which selects pods. This field follows standard label selector semantics; if present but empty, - it selects all pods. \n If NamespaceSelector + it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector + a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. - Otherwise it selects the Pods matching PodSelector - in the policy's own Namespace." + Otherwise it selects the pods matching podSelector + in the policy's own namespace." properties: matchExpressions: description: matchExpressions is a list @@ -2499,12 +2496,12 @@ spec: type: object type: array ingress: - description: List of ingress rules to be applied to the - selected pods. Traffic is allowed to a pod if there are - no NetworkPolicies selecting the pod (and cluster policy - otherwise allows the traffic), OR if the traffic source - is the pod's local node, OR if the traffic matches at - least one ingress rule across all of the NetworkPolicy + description: ingress is a list of ingress rules to be applied + to the selected pods. Traffic is allowed to a pod if there + are no NetworkPolicies selecting the pod (and cluster + policy otherwise allows the traffic), OR if the traffic + source is the pod's local node, OR if the traffic matches + at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects @@ -2516,35 +2513,36 @@ spec: match both ports and from. properties: from: - description: List of sources which should be able - to access the pods selected for this rule. Items - in this list are combined using a logical OR operation. - If this field is empty or missing, this rule matches - all sources (traffic not restricted by source). - If this field is present and contains at least one - item, this rule allows traffic only if the traffic - matches at least one item in the from list. + description: from is a list of sources which should + be able to access the pods selected for this rule. + Items in this list are combined using a logical + OR operation. If this field is empty or missing, + this rule matches all sources (traffic not restricted + by source). If this field is present and contains + at least one item, this rule allows traffic only + if the traffic matches at least one item in the + from list. items: description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing - the IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" + description: cidr is a string representing + the IPBlock Valid examples are "192.168.1.0/24" + or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs + description: except is a slice of CIDRs that should not be included within an - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" Except values will - be rejected if they are outside the CIDR + IPBlock Valid examples are "192.168.1.0/24" + or "2001:db8::/64" Except values will + be rejected if they are outside the cidr range items: type: string @@ -2553,15 +2551,15 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label - selector semantics; if present but empty, - it selects all namespaces. \n If PodSelector - is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector - in the Namespaces selected by NamespaceSelector. - Otherwise it selects all Pods in the Namespaces - selected by NamespaceSelector." + description: "namespaceSelector selects namespaces + using cluster-scoped labels. This field follows + standard label selector semantics; if present + but empty, it selects all namespaces. \n If + podSelector is also set, then the NetworkPolicyPeer + as a whole selects the pods matching podSelector + in the namespaces selected by namespaceSelector. + Otherwise it selects all pods in the namespaces + selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list @@ -2613,15 +2611,15 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which - selects Pods. This field follows standard + description: "podSelector is a label selector + which selects pods. This field follows standard label selector semantics; if present but empty, - it selects all pods. \n If NamespaceSelector + it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as - a whole selects the Pods matching PodSelector + a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. - Otherwise it selects the Pods matching PodSelector - in the policy's own Namespace." + Otherwise it selects the pods matching podSelector + in the policy's own namespace." properties: matchExpressions: description: matchExpressions is a list @@ -2675,59 +2673,59 @@ spec: type: object type: array ports: - description: List of ports which should be made accessible - on the pods selected for this rule. Each item in - this list is combined using a logical OR. If this - field is empty or missing, this rule matches all - ports (traffic not restricted by port). If this - field is present and contains at least one item, - then this rule allows traffic only if the traffic - matches at least one port in the list. + description: ports is a list of ports which should + be made accessible on the pods selected for this + rule. Each item in this list is combined using a + logical OR. If this field is empty or missing, this + rule matches all ports (traffic not restricted by + port). If this field is present and contains at + least one item, then this rule allows traffic only + if the traffic matches at least one port in the + list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range - of ports from port to endPort, inclusive, + description: endPort indicates that the range + of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be - equal or greater than port. This feature is - in Beta state and is enabled by default. It - can be disabled using the Feature Gate "NetworkPolicyEndPort". + equal or greater than port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. - This can either be a numerical or named port - on a pod. If this field is not provided, this - matches all port names and numbers. If present, - only traffic on the specified protocol AND - port will be matched. + description: port represents the port on the + given protocol. This can either be a numerical + or named port on a pod. If this field is not + provided, this matches all port names and + numbers. If present, only traffic on the specified + protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) - which traffic must match. If not specified, - this field defaults to TCP. + description: protocol represents the protocol + (TCP, UDP, or SCTP) which traffic must match. + If not specified, this field defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy - object applies. The array of ingress rules is applied - to any pods selected by this field. Multiple network policies - can select the same set of pods. In this case, the ingress - rules for each are combined additively. This field is - NOT optional and follows standard label selector semantics. - An empty podSelector matches all pods in this namespace. + description: podSelector selects the pods to which this + NetworkPolicy object applies. The array of ingress rules + is applied to any pods selected by this field. Multiple + network policies can select the same set of pods. In this + case, the ingress rules for each are combined additively. + This field is NOT optional and follows standard label + selector semantics. An empty podSelector matches all pods + in this namespace. properties: matchExpressions: description: matchExpressions is a list of label selector @@ -2773,20 +2771,20 @@ spec: type: object x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates - to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", - "Egress"]. If this field is not specified, it will default - based on the existence of Ingress or Egress rules; policies - that contain an Egress section are assumed to affect Egress, - and all policies (whether or not they contain an Ingress - section) are assumed to affect Ingress. If you want to - write an egress-only policy, you must explicitly specify - policyTypes [ "Egress" ]. Likewise, if you want to write - a policy that specifies that no egress is allowed, you - must specify a policyTypes value that include "Egress" - (since such a policy would not include an Egress section - and would otherwise default to just [ "Ingress" ]). This - field is beta-level in 1.8 + description: policyTypes is a list of rule types that the + NetworkPolicy relates to. Valid options are ["Ingress"], + ["Egress"], or ["Ingress", "Egress"]. If this field is + not specified, it will default based on the existence + of ingress or egress rules; policies that contain an egress + section are assumed to affect egress, and all policies + (whether or not they contain an ingress section) are assumed + to affect ingress. If you want to write an egress-only + policy, you must explicitly specify policyTypes [ "Egress" + ]. Likewise, if you want to write a policy that specifies + that no egress is allowed, you must specify a policyTypes + value that include "Egress" (since such a policy would + not include an egress section and would otherwise default + to just [ "Ingress" ]). This field is beta-level in 1.8 items: description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 diff --git a/controllers/resources/global.go b/controllers/resources/global.go index 70725720..6bcaaa93 100644 --- a/controllers/resources/global.go +++ b/controllers/resources/global.go @@ -174,7 +174,7 @@ func (r *Global) reconcileNormal(ctx context.Context, tntResource *capsulev1beta return reconcile.Result{}, err } - if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), processedItems) { + if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), sets.Set[string](processedItems)) { tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) for _, item := range processedItems.List() { diff --git a/controllers/resources/namespaced.go b/controllers/resources/namespaced.go index 68e6c03f..e9b0ae80 100644 --- a/controllers/resources/namespaced.go +++ b/controllers/resources/namespaced.go @@ -132,7 +132,7 @@ func (r *Namespaced) reconcileNormal(ctx context.Context, tntResource *capsulev1 return reconcile.Result{}, err } - if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), processedItems) { + if r.processor.HandlePruning(ctx, tntResource.Status.ProcessedItems.AsSet(), sets.Set[string](processedItems)) { tntResource.Status.ProcessedItems = make([]capsulev1beta2.ObjectReferenceStatus, 0, len(processedItems)) for _, item := range processedItems.List() { diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go index 37388c49..0ebd5b71 100644 --- a/controllers/resources/processor.go +++ b/controllers/resources/processor.go @@ -34,7 +34,7 @@ type Processor struct { client client.Client } -func (r *Processor) HandlePruning(ctx context.Context, current, desired sets.String) (updateStatus bool) { +func (r *Processor) HandlePruning(ctx context.Context, current, desired sets.Set[string]) (updateStatus bool) { log := ctrllog.FromContext(ctx) diff := current.Difference(desired) diff --git a/controllers/tls/manager.go b/controllers/tls/manager.go index 9a043f1c..f788e360 100644 --- a/controllers/tls/manager.go +++ b/controllers/tls/manager.go @@ -234,8 +234,8 @@ func (r *Reconciler) updateTenantCustomResourceDefinition(ctx context.Context, n Service: &apiextensionsv1.ServiceReference{ Namespace: r.Namespace, Name: "capsule-webhook-service", - Path: pointer.StringPtr("/convert"), - Port: pointer.Int32Ptr(443), + Path: pointer.String("/convert"), + Port: pointer.Int32(443), }, CABundle: caBundle, }, diff --git a/e2e/disable_externalname_test.go b/e2e/disable_externalname_test.go index a8b4e6fe..83f594c4 100644 --- a/e2e/disable_externalname_test.go +++ b/e2e/disable_externalname_test.go @@ -33,7 +33,7 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan }, ServiceOptions: &api.ServiceOptions{ AllowedServices: &api.AllowedServices{ - ExternalName: pointer.BoolPtr(false), + ExternalName: pointer.Bool(false), }, }, }, diff --git a/e2e/disable_loadbalancer_test.go b/e2e/disable_loadbalancer_test.go index 29cfdc35..4beb160f 100644 --- a/e2e/disable_loadbalancer_test.go +++ b/e2e/disable_loadbalancer_test.go @@ -33,7 +33,7 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant }, ServiceOptions: &api.ServiceOptions{ AllowedServices: &api.AllowedServices{ - LoadBalancer: pointer.BoolPtr(false), + LoadBalancer: pointer.Bool(false), }, }, }, diff --git a/e2e/disable_node_ports_test.go b/e2e/disable_node_ports_test.go index 10f670d6..b55b6a52 100644 --- a/e2e/disable_node_ports_test.go +++ b/e2e/disable_node_ports_test.go @@ -33,7 +33,7 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", f }, ServiceOptions: &api.ServiceOptions{ AllowedServices: &api.AllowedServices{ - NodePort: pointer.BoolPtr(false), + NodePort: pointer.Bool(false), }, }, }, diff --git a/e2e/enable_loadbalancer_test.go b/e2e/enable_loadbalancer_test.go index 8990e162..18ffeea1 100644 --- a/e2e/enable_loadbalancer_test.go +++ b/e2e/enable_loadbalancer_test.go @@ -33,7 +33,7 @@ var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant" }, ServiceOptions: &api.ServiceOptions{ AllowedServices: &api.AllowedServices{ - LoadBalancer: pointer.BoolPtr(true), + LoadBalancer: pointer.Bool(true), }, }, }, diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index 71b912be..af601e7f 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -135,7 +135,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", Name: "denied-ingress", }, Spec: extensionsv1beta1.IngressSpec{ - IngressClassName: pointer.StringPtr("the-worst-ingress-available"), + IngressClassName: pointer.String("the-worst-ingress-available"), Backend: &extensionsv1beta1.IngressBackend{ ServiceName: "foo", ServicePort: intstr.FromInt(8080), diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index 99e19118..35f5ab05 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -222,7 +222,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" Name: "denied-ingress", }, Spec: networkingv1.IngressSpec{ - IngressClassName: pointer.StringPtr("the-worst-ingress-available"), + IngressClassName: pointer.String("the-worst-ingress-available"), DefaultBackend: &networkingv1.IngressBackend{ Service: &networkingv1.IngressServiceBackend{ Name: "foo", diff --git a/e2e/overquota_namespace_test.go b/e2e/overquota_namespace_test.go index ab064b61..15a374f2 100644 --- a/e2e/overquota_namespace_test.go +++ b/e2e/overquota_namespace_test.go @@ -29,7 +29,7 @@ var _ = Describe("creating a Namespace in over-quota of three", func() { }, }, NamespaceOptions: &capsulev1beta2.NamespaceOptions{ - Quota: pointer.Int32Ptr(3), + Quota: pointer.Int32(3), }, }, } diff --git a/e2e/resource_quota_exceeded_test.go b/e2e/resource_quota_exceeded_test.go index 18e359f8..6b84a2d0 100644 --- a/e2e/resource_quota_exceeded_test.go +++ b/e2e/resource_quota_exceeded_test.go @@ -8,6 +8,7 @@ package e2e import ( "context" "fmt" + "github.com/clastix/capsule/pkg/api" . "github.com/onsi/ginkgo" @@ -134,7 +135,7 @@ var _ = Describe("exceeding a Tenant resource quota", func() { Name: "my-pause", }, Spec: appsv1.DeploymentSpec{ - Replicas: pointer.Int32Ptr(5), + Replicas: pointer.Int32(5), Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "app": "pause", diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index f9611dae..0db3959e 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -272,8 +272,8 @@ var _ = Describe("adding metadata to Service objects", func() { }, Ports: []discoveryv1beta1.EndpointPort{ { - Name: pointer.StringPtr("foo"), - Port: pointer.Int32Ptr(9999), + Name: pointer.String("foo"), + Port: pointer.Int32(9999), }, }, } @@ -291,8 +291,8 @@ var _ = Describe("adding metadata to Service objects", func() { }, Ports: []discoveryv1.EndpointPort{ { - Name: pointer.StringPtr("foo"), - Port: pointer.Int32Ptr(9999), + Name: pointer.String("foo"), + Port: pointer.Int32(9999), }, }, } diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go index d40d81df..4a7c1016 100644 --- a/e2e/storage_class_test.go +++ b/e2e/storage_class_test.go @@ -236,7 +236,7 @@ var _ = Describe("when Tenant handles Storage classes", func() { Name: c, }, Spec: corev1.PersistentVolumeClaimSpec{ - StorageClassName: pointer.StringPtr(c), + StorageClassName: pointer.String(c), AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, Resources: corev1.ResourceRequirements{ Requests: map[corev1.ResourceName]resource.Quantity{ diff --git a/pkg/indexer/ingress/hostname_path.go b/pkg/indexer/ingress/hostname_path.go index 2858614a..e717d0ea 100644 --- a/pkg/indexer/ingress/hostname_path.go +++ b/pkg/indexer/ingress/hostname_path.go @@ -33,7 +33,7 @@ func (s HostnamePath) Field() string { func (s HostnamePath) Func() client.IndexerFunc { return func(object client.Object) (entries []string) { - hostPathMap := make(map[string]sets.String) + hostPathMap := make(map[string]sets.Set[string]) switch ing := object.(type) { case *networkingv1.Ingress: diff --git a/pkg/indexer/ingress/utils.go b/pkg/indexer/ingress/utils.go index fa008f92..a4c893e3 100644 --- a/pkg/indexer/ingress/utils.go +++ b/pkg/indexer/ingress/utils.go @@ -10,8 +10,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" ) -func hostPathMapForExtensionsV1Beta1(ing *extensionsv1beta1.Ingress) map[string]sets.String { - hostPathMap := make(map[string]sets.String) +func hostPathMapForExtensionsV1Beta1(ing *extensionsv1beta1.Ingress) map[string]sets.Set[string] { + hostPathMap := make(map[string]sets.Set[string]) for _, r := range ing.Spec.Rules { if r.HTTP == nil { @@ -19,7 +19,7 @@ func hostPathMapForExtensionsV1Beta1(ing *extensionsv1beta1.Ingress) map[string] } if _, ok := hostPathMap[r.Host]; !ok { - hostPathMap[r.Host] = sets.NewString() + hostPathMap[r.Host] = sets.New[string]() } for _, path := range r.HTTP.Paths { @@ -30,8 +30,8 @@ func hostPathMapForExtensionsV1Beta1(ing *extensionsv1beta1.Ingress) map[string] return hostPathMap } -func hostPathMapForNetworkingV1Beta1(ing *networkingv1beta1.Ingress) map[string]sets.String { - hostPathMap := make(map[string]sets.String) +func hostPathMapForNetworkingV1Beta1(ing *networkingv1beta1.Ingress) map[string]sets.Set[string] { + hostPathMap := make(map[string]sets.Set[string]) for _, r := range ing.Spec.Rules { if r.HTTP == nil { @@ -39,7 +39,7 @@ func hostPathMapForNetworkingV1Beta1(ing *networkingv1beta1.Ingress) map[string] } if _, ok := hostPathMap[r.Host]; !ok { - hostPathMap[r.Host] = sets.NewString() + hostPathMap[r.Host] = sets.New[string]() } for _, path := range r.HTTP.Paths { @@ -50,8 +50,8 @@ func hostPathMapForNetworkingV1Beta1(ing *networkingv1beta1.Ingress) map[string] return hostPathMap } -func hostPathMapForNetworkingV1(ing *networkingv1.Ingress) map[string]sets.String { - hostPathMap := make(map[string]sets.String) +func hostPathMapForNetworkingV1(ing *networkingv1.Ingress) map[string]sets.Set[string] { + hostPathMap := make(map[string]sets.Set[string]) for _, r := range ing.Spec.Rules { if r.HTTP == nil { @@ -59,7 +59,7 @@ func hostPathMapForNetworkingV1(ing *networkingv1.Ingress) map[string]sets.Strin } if _, ok := hostPathMap[r.Host]; !ok { - hostPathMap[r.Host] = sets.NewString() + hostPathMap[r.Host] = sets.New[string]() } for _, path := range r.HTTP.Paths { diff --git a/pkg/webhook/ingress/types.go b/pkg/webhook/ingress/types.go index 3bf84eb0..63857491 100644 --- a/pkg/webhook/ingress/types.go +++ b/pkg/webhook/ingress/types.go @@ -20,7 +20,7 @@ type Ingress interface { IngressClass() *string Namespace() string Name() string - HostnamePathsPairs() map[string]sets.String + HostnamePathsPairs() map[string]sets.Set[string] SetIngressClass(string) SetNamespace(string) } @@ -69,14 +69,14 @@ func (n NetworkingV1) SetNamespace(ns string) { } //nolint:dupl -func (n NetworkingV1) HostnamePathsPairs() (pairs map[string]sets.String) { - pairs = make(map[string]sets.String) +func (n NetworkingV1) HostnamePathsPairs() (pairs map[string]sets.Set[string]) { + pairs = make(map[string]sets.Set[string]) for _, rule := range n.Spec.Rules { host := rule.Host if _, ok := pairs[host]; !ok { - pairs[host] = sets.NewString() + pairs[host] = sets.New[string]() } if http := rule.IngressRuleValue.HTTP; http != nil { @@ -139,14 +139,14 @@ func (n NetworkingV1Beta1) SetNamespace(ns string) { } //nolint:dupl -func (n NetworkingV1Beta1) HostnamePathsPairs() (pairs map[string]sets.String) { - pairs = make(map[string]sets.String) +func (n NetworkingV1Beta1) HostnamePathsPairs() (pairs map[string]sets.Set[string]) { + pairs = make(map[string]sets.Set[string]) for _, rule := range n.Spec.Rules { host := rule.Host if _, ok := pairs[host]; !ok { - pairs[host] = sets.NewString() + pairs[host] = sets.New[string]() } if http := rule.IngressRuleValue.HTTP; http != nil { @@ -207,14 +207,14 @@ func (e Extension) Namespace() string { } //nolint:dupl -func (e Extension) HostnamePathsPairs() (pairs map[string]sets.String) { - pairs = make(map[string]sets.String) +func (e Extension) HostnamePathsPairs() (pairs map[string]sets.Set[string]) { + pairs = make(map[string]sets.Set[string]) for _, rule := range e.Spec.Rules { host := rule.Host if _, ok := pairs[host]; !ok { - pairs[host] = sets.NewString() + pairs[host] = sets.New[string]() } if http := rule.IngressRuleValue.HTTP; http != nil { diff --git a/pkg/webhook/ingress/validate_hostnames.go b/pkg/webhook/ingress/validate_hostnames.go index c854953e..9975f3b1 100644 --- a/pkg/webhook/ingress/validate_hostnames.go +++ b/pkg/webhook/ingress/validate_hostnames.go @@ -63,7 +63,7 @@ func (r *hostnames) validate(ctx context.Context, client client.Client, req admi return nil } - hostnameList := sets.NewString() + hostnameList := sets.New[string]() for hostname := range ingress.HostnamePathsPairs() { hostnameList.Insert(hostname) } @@ -85,20 +85,20 @@ func (r *hostnames) validate(ctx context.Context, client client.Client, req admi return utils.ErroredResponse(err) } -func (r *hostnames) validateHostnames(tenant capsulev1beta2.Tenant, hostnames sets.String) error { +func (r *hostnames) validateHostnames(tenant capsulev1beta2.Tenant, hostnames sets.Set[string]) error { if tenant.Spec.IngressOptions.AllowedHostnames == nil { return nil } var valid, matched bool - tenantHostnameSet := sets.NewString(tenant.Spec.IngressOptions.AllowedHostnames.Exact...) + tenantHostnameSet := sets.New[string](tenant.Spec.IngressOptions.AllowedHostnames.Exact...) var invalidHostnames []string if len(hostnames) > 0 { if diff := hostnames.Difference(tenantHostnameSet); len(diff) > 0 { - invalidHostnames = append(invalidHostnames, diff.List()...) + invalidHostnames = append(invalidHostnames, diff.UnsortedList()...) } if len(invalidHostnames) == 0 { From 272d6f61c5e50e247cd6472509703c5bce2a313a Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 25 May 2023 09:55:32 +0200 Subject: [PATCH 141/153] feat: detecting group discovery error for indexers --- e2e/disable_ingress_wildcard_test.go | 12 +++---- e2e/ingress_class_extensions_test.go | 24 +++++--------- e2e/ingress_class_networking_test.go | 33 +++++++------------ ..._hostnames_collision_cluster_scope_test.go | 9 ++--- ...gress_hostnames_collision_disabled_test.go | 9 ++--- ...ostnames_collision_namespace_scope_test.go | 9 ++--- ...s_hostnames_collision_tenant_scope_test.go | 9 ++--- e2e/ingress_hostnames_test.go | 21 ++++-------- e2e/service_metadata_test.go | 9 ++--- pkg/indexer/indexer.go | 6 ++-- pkg/utils/errors.go | 16 +++++++++ 11 files changed, 63 insertions(+), 94 deletions(-) create mode 100644 pkg/utils/errors.go diff --git a/e2e/disable_ingress_wildcard_test.go b/e2e/disable_ingress_wildcard_test.go index 4c327f17..9d9f234f 100644 --- a/e2e/disable_ingress_wildcard_test.go +++ b/e2e/disable_ingress_wildcard_test.go @@ -7,7 +7,6 @@ package e2e import ( "context" - "errors" "fmt" . "github.com/onsi/ginkgo" @@ -15,11 +14,11 @@ import ( extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" networkingv1beta1 "k8s.io/api/networking/v1beta1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" + "github.com/clastix/capsule/pkg/utils" ) var _ = Describe("creating an Ingress with a wildcard when it is denied for the Tenant", func() { @@ -54,8 +53,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the It("should fail creating an extensions/v1beta1 Ingress with a wildcard hostname", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -135,8 +133,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the It("should fail creating an networking.k8s.io/v1beta1 Ingress with a wildcard hostname", func() { if err := k8sClient.List(context.Background(), &networkingv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -216,8 +213,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the It("should fail creating an networking.k8s.io/v1 Ingress with a wildcard hostname", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index af601e7f..c0054663 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -7,19 +7,18 @@ package e2e import ( "context" - "errors" "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", func() { @@ -72,8 +71,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", By("non-specifying at all", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -96,8 +94,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }) By("defining as deprecated annotation", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -123,8 +120,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", }) By("using the ingressClassName", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -158,8 +154,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact { Eventually(func() (err error) { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -186,8 +181,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", It("should allow enabled class using the ingressClassName field", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -232,8 +226,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", Eventually(func() (err error) { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -267,8 +260,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", Eventually(func() (err error) { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index 35f5ab05..242e2b2c 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -7,7 +7,6 @@ package e2e import ( "context" - "errors" "fmt" "strconv" "strings" @@ -15,7 +14,6 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" @@ -25,6 +23,7 @@ import ( capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1", func() { @@ -158,8 +157,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("should block a non allowed class", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -241,8 +239,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("should allow enabled class using the deprecated annotation for networking.k8s.io/v1", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -281,8 +278,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("should allow enabled class using the ingressClassName field", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -319,8 +315,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("should allow enabled Ingress by regex using the deprecated annotation", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -358,8 +353,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("should allow enabled Ingress by regex using the ingressClassName field", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -395,8 +389,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("should allow enabled Ingress by selector using the deprecated annotation", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -451,8 +444,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("should allow enabled Ingress by selector using the ingressClassName field", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -535,8 +527,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("should mutate to default tenant IngressClass (class exists)", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -575,8 +566,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("shoult mutate to default tenant IngressClass although the cluster global one is not allowed", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -622,8 +612,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("should mutate to default tenant IngressClass although the cluster global one is allowed", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } diff --git a/e2e/ingress_hostnames_collision_cluster_scope_test.go b/e2e/ingress_hostnames_collision_cluster_scope_test.go index fa23e49f..0836489e 100644 --- a/e2e/ingress_hostnames_collision_cluster_scope_test.go +++ b/e2e/ingress_hostnames_collision_cluster_scope_test.go @@ -7,19 +7,18 @@ package e2e import ( "context" - "errors" "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) var _ = Describe("when handling Cluster scoped Ingress hostnames collision", func() { @@ -156,8 +155,7 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", fun By("testing networking.k8s.io", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -191,8 +189,7 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", fun By("testing extensions", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } diff --git a/e2e/ingress_hostnames_collision_disabled_test.go b/e2e/ingress_hostnames_collision_disabled_test.go index e8139ec7..ff8ccd09 100644 --- a/e2e/ingress_hostnames_collision_disabled_test.go +++ b/e2e/ingress_hostnames_collision_disabled_test.go @@ -7,19 +7,18 @@ package e2e import ( "context" - "errors" "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) var _ = Describe("when disabling Ingress hostnames collision", func() { @@ -130,8 +129,7 @@ var _ = Describe("when disabling Ingress hostnames collision", func() { By("testing networking.k8s.io", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -171,8 +169,7 @@ var _ = Describe("when disabling Ingress hostnames collision", func() { By("testing extensions", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } diff --git a/e2e/ingress_hostnames_collision_namespace_scope_test.go b/e2e/ingress_hostnames_collision_namespace_scope_test.go index 66b5eabf..3f76bd49 100644 --- a/e2e/ingress_hostnames_collision_namespace_scope_test.go +++ b/e2e/ingress_hostnames_collision_namespace_scope_test.go @@ -7,19 +7,18 @@ package e2e import ( "context" - "errors" "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) var _ = Describe("when handling Namespace scoped Ingress hostnames collision", func() { @@ -130,8 +129,7 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", f By("testing networking.k8s.io", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -172,8 +170,7 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", f By("testing extensions", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } diff --git a/e2e/ingress_hostnames_collision_tenant_scope_test.go b/e2e/ingress_hostnames_collision_tenant_scope_test.go index 7081ae02..4e430ebb 100644 --- a/e2e/ingress_hostnames_collision_tenant_scope_test.go +++ b/e2e/ingress_hostnames_collision_tenant_scope_test.go @@ -7,19 +7,18 @@ package e2e import ( "context" - "errors" "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func() { @@ -133,8 +132,7 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func By("testing networking.k8s.io", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -168,8 +166,7 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func By("testing extensions", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } diff --git a/e2e/ingress_hostnames_test.go b/e2e/ingress_hostnames_test.go index cabf671e..5824da7b 100644 --- a/e2e/ingress_hostnames_test.go +++ b/e2e/ingress_hostnames_test.go @@ -7,19 +7,18 @@ package e2e import ( "context" - "errors" "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) var _ = Describe("when Tenant handles Ingress hostnames", func() { @@ -129,8 +128,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { By("testing networking.k8s.io", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -152,8 +150,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { By("testing extensions", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -175,8 +172,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { By("testing networking.k8s.io", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -200,8 +196,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { By("testing extensions", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -225,8 +220,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { By("testing networking.k8s.io", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -250,8 +244,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() { By("testing extensions", func() { if err := k8sClient.List(context.Background(), &extensionsv1beta1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index 0db3959e..382bdb09 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -7,7 +7,6 @@ package e2e import ( "context" - "errors" "fmt" . "github.com/onsi/ginkgo" @@ -16,7 +15,6 @@ import ( discoveryv1 "k8s.io/api/discovery/v1" discoveryv1beta1 "k8s.io/api/discovery/v1beta1" networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" @@ -25,6 +23,7 @@ import ( capsulev1beta2 "github.com/clastix/capsule/api/v1beta2" "github.com/clastix/capsule/pkg/api" + "github.com/clastix/capsule/pkg/utils" ) var _ = Describe("adding metadata to Service objects", func() { @@ -222,8 +221,7 @@ var _ = Describe("adding metadata to Service objects", func() { It("should apply them to EndpointSlice in v1", func() { if err := k8sClient.List(context.Background(), &networkingv1.IngressList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } } @@ -254,8 +252,7 @@ var _ = Describe("adding metadata to Service objects", func() { var eps client.Object if err := k8sClient.List(context.Background(), &discoveryv1.EndpointSliceList{}); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error())) } diff --git a/pkg/indexer/indexer.go b/pkg/indexer/indexer.go index c2b3cdb5..e726ca8b 100644 --- a/pkg/indexer/indexer.go +++ b/pkg/indexer/indexer.go @@ -8,11 +8,9 @@ import ( "fmt" "github.com/go-logr/logr" - "github.com/pkg/errors" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" networkingv1beta1 "k8s.io/api/networking/v1beta1" - "k8s.io/apimachinery/pkg/api/meta" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -21,6 +19,7 @@ import ( "github.com/clastix/capsule/pkg/indexer/namespace" "github.com/clastix/capsule/pkg/indexer/tenant" "github.com/clastix/capsule/pkg/indexer/tenantresource" + "github.com/clastix/capsule/pkg/utils" ) type CustomIndexer interface { @@ -43,8 +42,7 @@ func AddToManager(ctx context.Context, log logr.Logger, mgr manager.Manager) err for _, f := range indexers { if err := mgr.GetFieldIndexer().IndexField(ctx, f.Object(), f.Field(), f.Func()); err != nil { - missingAPIError := &meta.NoKindMatchError{} - if errors.As(err, &missingAPIError) { + if utils.IsUnsupportedAPI(err) { log.Info(fmt.Sprintf("skipping setup of Indexer %T for object %T", f, f.Object()), "error", err.Error()) continue diff --git a/pkg/utils/errors.go b/pkg/utils/errors.go new file mode 100644 index 00000000..cc552035 --- /dev/null +++ b/pkg/utils/errors.go @@ -0,0 +1,16 @@ +// Copyright 2020-2021 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/discovery" +) + +func IsUnsupportedAPI(err error) bool { + missingAPIError, discoveryError := &meta.NoKindMatchError{}, &discovery.ErrGroupDiscoveryFailed{} + + return errors.As(err, &missingAPIError) || errors.As(err, &discoveryError) +} From a9503809881bdf1553c5ea101931da85d10a9ffa Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 25 May 2023 10:39:41 +0200 Subject: [PATCH 142/153] chore(ginkgo): updating to ginkgo v2 --- Makefile | 2 +- e2e/additional_role_bindings_test.go | 2 +- e2e/allowed_external_ips_test.go | 2 +- e2e/container_registry_test.go | 2 +- e2e/custom_capsule_group_test.go | 2 +- e2e/custom_resource_quota_test.go | 2 +- e2e/disable_externalname_test.go | 2 +- e2e/disable_ingress_wildcard_test.go | 2 +- e2e/disable_loadbalancer_test.go | 2 +- e2e/disable_node_ports_test.go | 2 +- e2e/dynamic_tenant_owner_clusterroles_test.go | 2 +- e2e/enable_loadbalancer_test.go | 2 +- e2e/enable_node_ports_test.go | 2 +- e2e/forbidden_annotations_regex_test.go | 4 +- e2e/force_tenant_prefix_test.go | 2 +- e2e/globaltenantresource_test.go | 2 +- e2e/imagepullpolicy_multiple_test.go | 2 +- e2e/imagepullpolicy_single_test.go | 2 +- e2e/ingress_class_extensions_test.go | 2 +- e2e/ingress_class_networking_test.go | 2 +- ..._hostnames_collision_cluster_scope_test.go | 2 +- ...gress_hostnames_collision_disabled_test.go | 2 +- ...ostnames_collision_namespace_scope_test.go | 2 +- ...s_hostnames_collision_tenant_scope_test.go | 2 +- e2e/ingress_hostnames_test.go | 2 +- e2e/missing_tenant_test.go | 2 +- e2e/namespace_additional_metadata_test.go | 2 +- e2e/namespace_capsule_label_test.go | 2 +- e2e/namespace_user_metadata_test.go | 2 +- e2e/new_namespace_test.go | 2 +- e2e/node_user_metadata_test.go | 2 +- e2e/overquota_namespace_test.go | 2 +- e2e/owner_webhooks_test.go | 2 +- e2e/pod_priority_class_test.go | 2 +- e2e/pod_runtime_class_test.go | 2 +- e2e/preventing_pv_cross_tenant_mount_test.go | 2 +- e2e/protected_namespace_regex_test.go | 2 +- e2e/resource_quota_exceeded_test.go | 2 +- e2e/sa_prevent_privilege_escalation_test.go | 2 +- e2e/selecting_non_owned_tenant_test.go | 3 +- e2e/selecting_tenant_fail_test.go | 2 +- e2e/selecting_tenant_with_label_test.go | 3 +- e2e/service_metadata_test.go | 2 +- e2e/storage_class_test.go | 2 +- e2e/suite_test.go | 20 ++++----- e2e/tenant_cordoning_test.go | 2 +- e2e/tenant_name_webhook_test.go | 2 +- e2e/tenant_protected_webhook_test.go | 2 +- e2e/tenant_resources_changes_test.go | 3 +- e2e/tenant_resources_test.go | 5 ++- e2e/tenantresource_test.go | 2 +- go.mod | 9 ++-- go.sum | 41 ++++++------------- 53 files changed, 81 insertions(+), 97 deletions(-) diff --git a/Makefile b/Makefile index 9b713617..892b2b0f 100644 --- a/Makefile +++ b/Makefile @@ -189,7 +189,7 @@ apidocs-gen: ## Download crdoc locally if necessary. GINKGO = $(shell pwd)/bin/ginkgo ginkgo: ## Download ginkgo locally if necessary. - $(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/ginkgo@v1.16.5) + $(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo@v2.9.5) CT = $(shell pwd)/bin/ct ct: ## Download ct locally if necessary. diff --git a/e2e/additional_role_bindings_test.go b/e2e/additional_role_bindings_test.go index e207a189..9bca98ba 100644 --- a/e2e/additional_role_bindings_test.go +++ b/e2e/additional_role_bindings_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/allowed_external_ips_test.go b/e2e/allowed_external_ips_test.go index 62fdfa1d..d5b0757e 100644 --- a/e2e/allowed_external_ips_test.go +++ b/e2e/allowed_external_ips_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/container_registry_test.go b/e2e/container_registry_test.go index 4a1a3bfb..9b499f24 100644 --- a/e2e/container_registry_test.go +++ b/e2e/container_registry_test.go @@ -9,7 +9,7 @@ import ( "context" "encoding/json" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/custom_capsule_group_test.go b/e2e/custom_capsule_group_test.go index d703fefb..750ece2f 100644 --- a/e2e/custom_capsule_group_test.go +++ b/e2e/custom_capsule_group_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/custom_resource_quota_test.go b/e2e/custom_resource_quota_test.go index 5354f801..dd056d0d 100644 --- a/e2e/custom_resource_quota_test.go +++ b/e2e/custom_resource_quota_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/disable_externalname_test.go b/e2e/disable_externalname_test.go index 83f594c4..819bc0af 100644 --- a/e2e/disable_externalname_test.go +++ b/e2e/disable_externalname_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/disable_ingress_wildcard_test.go b/e2e/disable_ingress_wildcard_test.go index 9d9f234f..38c6a7b6 100644 --- a/e2e/disable_ingress_wildcard_test.go +++ b/e2e/disable_ingress_wildcard_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" diff --git a/e2e/disable_loadbalancer_test.go b/e2e/disable_loadbalancer_test.go index 4beb160f..c4f52c78 100644 --- a/e2e/disable_loadbalancer_test.go +++ b/e2e/disable_loadbalancer_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/disable_node_ports_test.go b/e2e/disable_node_ports_test.go index b55b6a52..2e08dced 100644 --- a/e2e/disable_node_ports_test.go +++ b/e2e/disable_node_ports_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/dynamic_tenant_owner_clusterroles_test.go b/e2e/dynamic_tenant_owner_clusterroles_test.go index c491b4b0..36221053 100644 --- a/e2e/dynamic_tenant_owner_clusterroles_test.go +++ b/e2e/dynamic_tenant_owner_clusterroles_test.go @@ -9,7 +9,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/enable_loadbalancer_test.go b/e2e/enable_loadbalancer_test.go index 18ffeea1..f9ac51b4 100644 --- a/e2e/enable_loadbalancer_test.go +++ b/e2e/enable_loadbalancer_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/enable_node_ports_test.go b/e2e/enable_node_ports_test.go index 1c079439..764b0665 100644 --- a/e2e/enable_node_ports_test.go +++ b/e2e/enable_node_ports_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/forbidden_annotations_regex_test.go b/e2e/forbidden_annotations_regex_test.go index db45297a..f2797a84 100644 --- a/e2e/forbidden_annotations_regex_test.go +++ b/e2e/forbidden_annotations_regex_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -91,7 +91,7 @@ var _ = Describe("creating a tenant with various forbidden regexes", func() { EventuallyCreation(func() error { tnt.SetResourceVersion("") - + tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{ ForbiddenAnnotations: api.ForbiddenListSpec{ Regex: annotationValue, diff --git a/e2e/force_tenant_prefix_test.go b/e2e/force_tenant_prefix_test.go index 488c8682..ef763eb4 100644 --- a/e2e/force_tenant_prefix_test.go +++ b/e2e/force_tenant_prefix_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/globaltenantresource_test.go b/e2e/globaltenantresource_test.go index b0ebf1fe..2a3beff4 100644 --- a/e2e/globaltenantresource_test.go +++ b/e2e/globaltenantresource_test.go @@ -10,7 +10,7 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/imagepullpolicy_multiple_test.go b/e2e/imagepullpolicy_multiple_test.go index 78beaac9..65fdc18f 100644 --- a/e2e/imagepullpolicy_multiple_test.go +++ b/e2e/imagepullpolicy_multiple_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/imagepullpolicy_single_test.go b/e2e/imagepullpolicy_single_test.go index 1d3f873b..6143bf77 100644 --- a/e2e/imagepullpolicy_single_test.go +++ b/e2e/imagepullpolicy_single_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index c0054663..7171fc14 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index 242e2b2c..881a4b7d 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -11,7 +11,7 @@ import ( "strconv" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/ingress_hostnames_collision_cluster_scope_test.go b/e2e/ingress_hostnames_collision_cluster_scope_test.go index 0836489e..14d895c7 100644 --- a/e2e/ingress_hostnames_collision_cluster_scope_test.go +++ b/e2e/ingress_hostnames_collision_cluster_scope_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" diff --git a/e2e/ingress_hostnames_collision_disabled_test.go b/e2e/ingress_hostnames_collision_disabled_test.go index ff8ccd09..7b18ec4f 100644 --- a/e2e/ingress_hostnames_collision_disabled_test.go +++ b/e2e/ingress_hostnames_collision_disabled_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" diff --git a/e2e/ingress_hostnames_collision_namespace_scope_test.go b/e2e/ingress_hostnames_collision_namespace_scope_test.go index 3f76bd49..317f1ce8 100644 --- a/e2e/ingress_hostnames_collision_namespace_scope_test.go +++ b/e2e/ingress_hostnames_collision_namespace_scope_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" diff --git a/e2e/ingress_hostnames_collision_tenant_scope_test.go b/e2e/ingress_hostnames_collision_tenant_scope_test.go index 4e430ebb..9e460494 100644 --- a/e2e/ingress_hostnames_collision_tenant_scope_test.go +++ b/e2e/ingress_hostnames_collision_tenant_scope_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" diff --git a/e2e/ingress_hostnames_test.go b/e2e/ingress_hostnames_test.go index 5824da7b..0a115365 100644 --- a/e2e/ingress_hostnames_test.go +++ b/e2e/ingress_hostnames_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" networkingv1 "k8s.io/api/networking/v1" diff --git a/e2e/missing_tenant_test.go b/e2e/missing_tenant_test.go index 5202bb9b..fdc98d41 100644 --- a/e2e/missing_tenant_test.go +++ b/e2e/missing_tenant_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/namespace_additional_metadata_test.go b/e2e/namespace_additional_metadata_test.go index 87e00c19..4bac88bf 100644 --- a/e2e/namespace_additional_metadata_test.go +++ b/e2e/namespace_additional_metadata_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" diff --git a/e2e/namespace_capsule_label_test.go b/e2e/namespace_capsule_label_test.go index 21ac4582..aa858dfd 100644 --- a/e2e/namespace_capsule_label_test.go +++ b/e2e/namespace_capsule_label_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/namespace_user_metadata_test.go b/e2e/namespace_user_metadata_test.go index b59a77c2..7f7d5773 100644 --- a/e2e/namespace_user_metadata_test.go +++ b/e2e/namespace_user_metadata_test.go @@ -9,7 +9,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/new_namespace_test.go b/e2e/new_namespace_test.go index 6d6cfc20..21df0812 100644 --- a/e2e/new_namespace_test.go +++ b/e2e/new_namespace_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/node_user_metadata_test.go b/e2e/node_user_metadata_test.go index 1ded6b50..2a02ba18 100644 --- a/e2e/node_user_metadata_test.go +++ b/e2e/node_user_metadata_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" diff --git a/e2e/overquota_namespace_test.go b/e2e/overquota_namespace_test.go index 15a374f2..9587baa5 100644 --- a/e2e/overquota_namespace_test.go +++ b/e2e/overquota_namespace_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" diff --git a/e2e/owner_webhooks_test.go b/e2e/owner_webhooks_test.go index 57a990b5..bc1f8f85 100644 --- a/e2e/owner_webhooks_test.go +++ b/e2e/owner_webhooks_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index d4737650..0e96c555 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" schedulingv1 "k8s.io/api/scheduling/v1" diff --git a/e2e/pod_runtime_class_test.go b/e2e/pod_runtime_class_test.go index e6739da6..58c5339a 100644 --- a/e2e/pod_runtime_class_test.go +++ b/e2e/pod_runtime_class_test.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" nodev1 "k8s.io/api/node/v1" diff --git a/e2e/preventing_pv_cross_tenant_mount_test.go b/e2e/preventing_pv_cross_tenant_mount_test.go index c3834348..0618bb58 100644 --- a/e2e/preventing_pv_cross_tenant_mount_test.go +++ b/e2e/preventing_pv_cross_tenant_mount_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" diff --git a/e2e/protected_namespace_regex_test.go b/e2e/protected_namespace_regex_test.go index 1b878cf1..3b4279b9 100644 --- a/e2e/protected_namespace_regex_test.go +++ b/e2e/protected_namespace_regex_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/resource_quota_exceeded_test.go b/e2e/resource_quota_exceeded_test.go index 6b84a2d0..86813518 100644 --- a/e2e/resource_quota_exceeded_test.go +++ b/e2e/resource_quota_exceeded_test.go @@ -11,7 +11,7 @@ import ( "github.com/clastix/capsule/pkg/api" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" diff --git a/e2e/sa_prevent_privilege_escalation_test.go b/e2e/sa_prevent_privilege_escalation_test.go index f2bde523..b31a4a86 100644 --- a/e2e/sa_prevent_privilege_escalation_test.go +++ b/e2e/sa_prevent_privilege_escalation_test.go @@ -10,7 +10,7 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/selecting_non_owned_tenant_test.go b/e2e/selecting_non_owned_tenant_test.go index 3709db27..8734dbd5 100644 --- a/e2e/selecting_non_owned_tenant_test.go +++ b/e2e/selecting_non_owned_tenant_test.go @@ -7,9 +7,10 @@ package e2e import ( "context" + "github.com/clastix/capsule/pkg/utils" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/selecting_tenant_fail_test.go b/e2e/selecting_tenant_fail_test.go index 6d4242b7..95253c45 100644 --- a/e2e/selecting_tenant_fail_test.go +++ b/e2e/selecting_tenant_fail_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/selecting_tenant_with_label_test.go b/e2e/selecting_tenant_with_label_test.go index 9398852b..093a2712 100644 --- a/e2e/selecting_tenant_with_label_test.go +++ b/e2e/selecting_tenant_with_label_test.go @@ -7,9 +7,10 @@ package e2e import ( "context" + "github.com/clastix/capsule/pkg/utils" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index 382bdb09..b27524fc 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go index 4a7c1016..c182c4ea 100644 --- a/e2e/storage_class_test.go +++ b/e2e/storage_class_test.go @@ -13,7 +13,7 @@ import ( "strconv" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" diff --git a/e2e/suite_test.go b/e2e/suite_test.go index b832327e..55a6e370 100644 --- a/e2e/suite_test.go +++ b/e2e/suite_test.go @@ -8,8 +8,7 @@ package e2e import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" @@ -27,21 +26,18 @@ import ( // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. var ( - cfg *rest.Config - k8sClient client.Client - testEnv *envtest.Environment - tenantRoleBindingNames = []string{"namespace:admin", "namespace-deleter"} + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment ) func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) - RunSpecsWithDefaultAndCustomReporters(t, - "Controller Suite", - []Reporter{&reporters.JUnitReporter{}}) + RunSpecs(t, "Controller Suite") } -var _ = BeforeSuite(func(done Done) { +var _ = BeforeSuite(func() { logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter))) By("bootstrapping test environment") @@ -61,9 +57,7 @@ var _ = BeforeSuite(func(done Done) { k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).ToNot(HaveOccurred()) Expect(k8sClient).ToNot(BeNil()) - - close(done) -}, 60) +}) var _ = AfterSuite(func() { By("tearing down the test environment") diff --git a/e2e/tenant_cordoning_test.go b/e2e/tenant_cordoning_test.go index 069275fb..95a77228 100644 --- a/e2e/tenant_cordoning_test.go +++ b/e2e/tenant_cordoning_test.go @@ -9,7 +9,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/tenant_name_webhook_test.go b/e2e/tenant_name_webhook_test.go index d555bf29..91abe378 100644 --- a/e2e/tenant_name_webhook_test.go +++ b/e2e/tenant_name_webhook_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/e2e/tenant_protected_webhook_test.go b/e2e/tenant_protected_webhook_test.go index fcf78842..f41175a3 100644 --- a/e2e/tenant_protected_webhook_test.go +++ b/e2e/tenant_protected_webhook_test.go @@ -8,7 +8,7 @@ package e2e import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" diff --git a/e2e/tenant_resources_changes_test.go b/e2e/tenant_resources_changes_test.go index 4eb08757..17a7e713 100644 --- a/e2e/tenant_resources_changes_test.go +++ b/e2e/tenant_resources_changes_test.go @@ -8,9 +8,10 @@ package e2e import ( "context" "fmt" + "github.com/clastix/capsule/pkg/api" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" diff --git a/e2e/tenant_resources_test.go b/e2e/tenant_resources_test.go index c8f6805d..5337775e 100644 --- a/e2e/tenant_resources_test.go +++ b/e2e/tenant_resources_test.go @@ -8,10 +8,11 @@ package e2e import ( "context" "fmt" - "github.com/clastix/capsule/pkg/api" "strings" - . "github.com/onsi/ginkgo" + "github.com/clastix/capsule/pkg/api" + + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" diff --git a/e2e/tenantresource_test.go b/e2e/tenantresource_test.go index b3afe88c..478db0f8 100644 --- a/e2e/tenantresource_test.go +++ b/e2e/tenantresource_test.go @@ -11,7 +11,7 @@ import ( "math/rand" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/go.mod b/go.mod index a01f5ad3..8b3d2b31 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,14 @@ go 1.19 require ( github.com/go-logr/logr v1.2.4 github.com/hashicorp/go-multierror v1.1.0 - github.com/onsi/ginkgo v1.16.5 + github.com/onsi/ginkgo/v2 v2.9.5 github.com/onsi/gomega v1.27.7 github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 github.com/valyala/fasttemplate v1.2.2 go.uber.org/zap v1.24.0 - golang.org/x/sync v0.1.0 + golang.org/x/sync v0.2.0 k8s.io/api v0.27.2 k8s.io/apiextensions-apiserver v0.27.2 k8s.io/apimachinery v0.27.2 @@ -34,6 +34,7 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobuffalo/flect v1.0.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -41,6 +42,7 @@ require ( github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/imdario/mergo v0.3.13 // indirect @@ -51,7 +53,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nxadm/tail v1.4.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.15.1 // indirect github.com/prometheus/client_model v0.4.0 // indirect @@ -66,11 +67,11 @@ require ( golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.9.1 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/component-base v0.27.2 // indirect diff --git a/go.sum b/go.sum index 283b0062..02e269f4 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,9 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -45,8 +48,6 @@ github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCv github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -61,8 +62,8 @@ github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTr github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -102,6 +103,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -110,8 +112,8 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -143,16 +145,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -184,6 +178,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -222,9 +217,9 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -232,7 +227,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -250,19 +244,15 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -288,10 +278,10 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -334,16 +324,11 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 3523023e72a0478a7ba4dc907803564dcb286445 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 25 May 2023 14:03:39 +0200 Subject: [PATCH 143/153] chore(kustomize): updated descriptions --- config/install.yaml | 144 ++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/config/install.yaml b/config/install.yaml index ffe27a14..2470c90a 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -790,44 +790,44 @@ spec: description: NetworkPolicySpec provides the specification of a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + description: egress is a list of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 items: description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 properties: ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + description: to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. items: description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range items: type: string type: array @@ -835,7 +835,7 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -866,7 +866,7 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -901,23 +901,23 @@ spec: type: object type: array ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + description: ingress is a list of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) items: description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. properties: from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + description: from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. items: description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range items: type: string type: array @@ -925,7 +925,7 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -956,7 +956,7 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -989,30 +989,30 @@ spec: type: object type: array ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + description: podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -1043,7 +1043,7 @@ spec: type: object x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + description: policyTypes is a list of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of ingress or egress rules; policies that contain an egress section are assumed to affect egress, and all policies (whether or not they contain an ingress section) are assumed to affect ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 type: string @@ -1378,44 +1378,44 @@ spec: description: NetworkPolicySpec provides the specification of a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + description: egress is a list of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 items: description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 properties: ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + description: to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. items: description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range items: type: string type: array @@ -1423,7 +1423,7 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -1454,7 +1454,7 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -1489,23 +1489,23 @@ spec: type: object type: array ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + description: ingress is a list of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) items: description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. properties: from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + description: from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. items: description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range items: type: string type: array @@ -1513,7 +1513,7 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -1544,7 +1544,7 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -1577,30 +1577,30 @@ spec: type: object type: array ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + description: podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -1631,7 +1631,7 @@ spec: type: object x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + description: policyTypes is a list of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of ingress or egress rules; policies that contain an egress section are assumed to affect egress, and all policies (whether or not they contain an ingress section) are assumed to affect ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 type: string @@ -2116,44 +2116,44 @@ spec: description: NetworkPolicySpec provides the specification of a NetworkPolicy properties: egress: - description: List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + description: egress is a list of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 items: description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 properties: ports: - description: List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. type: string type: object type: array to: - description: List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + description: to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. items: description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range items: type: string type: array @@ -2161,7 +2161,7 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -2192,7 +2192,7 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -2227,23 +2227,23 @@ spec: type: object type: array ingress: - description: List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + description: ingress is a list of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) items: description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. properties: from: - description: List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + description: from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. items: description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed properties: ipBlock: - description: IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. properties: cidr: - description: CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" + description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" type: string except: - description: Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range + description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range items: type: string type: array @@ -2251,7 +2251,7 @@ spec: - cidr type: object namespaceSelector: - description: "Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector." + description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -2282,7 +2282,7 @@ spec: type: object x-kubernetes-map-type: atomic podSelector: - description: "This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace." + description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace." properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -2315,30 +2315,30 @@ spec: type: object type: array ports: - description: List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + description: ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. items: description: NetworkPolicyPort describes a port to allow traffic on properties: endPort: - description: If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort". + description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. format: int32 type: integer port: anyOf: - type: integer - type: string - description: The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. x-kubernetes-int-or-string: true protocol: default: TCP - description: The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. type: string type: object type: array type: object type: array podSelector: - description: Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + description: podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -2369,7 +2369,7 @@ spec: type: object x-kubernetes-map-type: atomic policyTypes: - description: List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + description: policyTypes is a list of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of ingress or egress rules; policies that contain an egress section are assumed to affect egress, and all policies (whether or not they contain an ingress section) are assumed to affect ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 items: description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 type: string From bd39055f35f61e04247c0023f4e519950556ec48 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 25 May 2023 14:05:25 +0200 Subject: [PATCH 144/153] chore(helm): updated descriptions --- charts/capsule/crds/tenant-crd.yaml | 1431 +++++++++++++-------------- 1 file changed, 714 insertions(+), 717 deletions(-) diff --git a/charts/capsule/crds/tenant-crd.yaml b/charts/capsule/crds/tenant-crd.yaml index d1bbdd10..314016fb 100644 --- a/charts/capsule/crds/tenant-crd.yaml +++ b/charts/capsule/crds/tenant-crd.yaml @@ -62,14 +62,10 @@ spec: description: Tenant is the Schema for the tenants API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -77,6 +73,7 @@ spec: description: TenantSpec defines the desired state of Tenant. properties: additionalRoleBindings: + description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. items: properties: clusterRoleName: @@ -84,31 +81,19 @@ spec: subjects: description: kubebuilder:validation:Minimum=1 items: - description: Subject contains a reference to the object or - user identities a role binding applies to. This can either - hold a direct API object reference, or a value for non-objects - such as user and group names. + description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names. properties: apiGroup: - description: APIGroup holds the API group of the referenced - subject. Defaults to "" for ServiceAccount subjects. - Defaults to "rbac.authorization.k8s.io" for User and - Group subjects. + description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects. type: string kind: - description: Kind of object being referenced. Values defined - by this API group are "User", "Group", and "ServiceAccount". - If the Authorizer does not recognized the kind value, - the Authorizer should report an error. + description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error. type: string name: description: Name of the object being referenced. type: string namespace: - description: Namespace of the referenced object. If the - object kind is non-namespace, such as "User" or "Group", - and this value is not empty the Authorizer should report - an error. + description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error. type: string required: - kind @@ -122,6 +107,7 @@ spec: type: object type: array containerRegistries: + description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. properties: allowed: items: @@ -130,732 +116,704 @@ spec: allowedRegex: type: string type: object - externalServiceIPs: - properties: - allowed: - items: - pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ - type: string - type: array - required: - - allowed - type: object - ingressClasses: - properties: - allowed: - items: - type: string - type: array - allowedRegex: - type: string - type: object - ingressHostnames: - properties: - allowed: - items: - type: string - type: array - allowedRegex: - type: string - type: object - limitRanges: + cordoned: + description: Toggling the Tenant resources cordoning, when enable resources cannot be deleted. + type: boolean + imagePullPolicies: + description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. items: - description: LimitRangeSpec defines a min/max usage limit for resources - that match on kind. - properties: - limits: - description: Limits is the list of LimitRangeItem objects that - are enforced. - items: - description: LimitRangeItem defines a min/max usage limit - for any resource that matches on kind. - properties: - default: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Default resource requirement limit value - by resource name if resource limit is omitted. - type: object - defaultRequest: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: DefaultRequest is the default resource requirement - request value by resource name if resource request is - omitted. - type: object - max: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Max usage constraints on this kind by resource - name. - type: object - maxLimitRequestRatio: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: MaxLimitRequestRatio if specified, the named - resource must have a request and limit that are both - non-zero where limit divided by request is less than - or equal to the enumerated value; this represents the - max burst for the named resource. - type: object - min: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: Min usage constraints on this kind by resource - name. - type: object - type: - description: Type of resource that this limit applies - to. - type: string - required: - - type - type: object - type: array - required: - - limits - type: object + enum: + - Always + - Never + - IfNotPresent + type: string type: array - namespaceQuota: - format: int32 - minimum: 1 - type: integer - namespacesMetadata: + ingressOptions: + description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. properties: - additionalAnnotations: - additionalProperties: - type: string + allowWildcardHostnames: + description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard. + type: boolean + allowedClasses: + description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. A default value can be specified, and all the Ingress resources created will inherit the declared class. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + default: + type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - additionalLabels: - additionalProperties: - type: string + x-kubernetes-map-type: atomic + allowedHostnames: + description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string type: object + hostnameCollisionScope: + default: Disabled + description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional." + enum: + - Cluster + - Tenant + - Namespace + - Disabled + type: string type: object - networkPolicies: - items: - description: NetworkPolicySpec provides the specification of a NetworkPolicy - properties: - egress: - description: List of egress rules to be applied to the selected - pods. Outgoing traffic is allowed if there are no NetworkPolicies - selecting the pod (and cluster policy otherwise allows the - traffic), OR if the traffic matches at least one egress rule - across all of the NetworkPolicy objects whose podSelector - matches the pod. If this field is empty then this NetworkPolicy - limits all outgoing traffic (and serves solely to ensure that - the pods it selects are isolated by default). This field is - beta-level in 1.8 - items: - description: NetworkPolicyEgressRule describes a particular - set of traffic that is allowed out of pods matched by a - NetworkPolicySpec's podSelector. The traffic must match - both ports and to. This type is beta-level in 1.8 - properties: - ports: - description: List of destination ports for outgoing traffic. - Each item in this list is combined using a logical OR. - If this field is empty or missing, this rule matches - all ports (traffic not restricted by port). If this - field is present and contains at least one item, then - this rule allows traffic only if the traffic matches - at least one port in the list. - items: - description: NetworkPolicyPort describes a port to allow - traffic on - properties: - endPort: - description: If set, indicates that the range of - ports from port to endPort, inclusive, should - be allowed by the policy. This field cannot be - defined if the port field is not defined or if - the port field is defined as a named (string) - port. The endPort must be equal or greater than - port. This feature is in Beta state and is enabled - by default. It can be disabled using the Feature - Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: + limitRanges: + description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: LimitRangeSpec defines a min/max usage limit for resources that match on kind. + properties: + limits: + description: Limits is the list of LimitRangeItem objects that are enforced. + items: + description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind. + properties: + default: + additionalProperties: anyOf: - type: integer - type: string - description: The port on the given protocol. This - can either be a numerical or named port on a pod. - If this field is not provided, this matches all - port names and numbers. If present, only traffic - on the specified protocol AND port will be matched. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) which - traffic must match. If not specified, this field - defaults to TCP. - type: string - type: object - type: array - to: - description: List of destinations for outgoing traffic - of pods selected for this rule. Items in this list are - combined using a logical OR operation. If this field - is empty or missing, this rule matches all destinations - (traffic not restricted by destination). If this field - is present and contains at least one item, this rule - allows traffic only if the traffic matches at least - one item in the to list. - items: - description: NetworkPolicyPeer describes a peer to allow - traffic to/from. Only certain combinations of fields - are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular - IPBlock. If this field is set then neither of - the other fields can be. - properties: - cidr: - description: CIDR is a string representing the - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" - type: string - except: - description: Except is a slice of CIDRs that - should not be included within an IP Block - Valid examples are "192.168.1.1/24" or "2001:db9::/64" - Except values will be rejected if they are - outside the CIDR range - items: - type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label selector - semantics; if present but empty, it selects all - namespaces. \n If PodSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects all - Pods in the Namespaces selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which selects - Pods. This field follows standard label selector - semantics; if present but empty, it selects all - pods. \n If NamespaceSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects the - Pods matching PodSelector in the policy's own - Namespace." - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - type: object - type: array - ingress: - description: List of ingress rules to be applied to the selected - pods. Traffic is allowed to a pod if there are no NetworkPolicies - selecting the pod (and cluster policy otherwise allows the - traffic), OR if the traffic source is the pod's local node, - OR if the traffic matches at least one ingress rule across - all of the NetworkPolicy objects whose podSelector matches - the pod. If this field is empty then this NetworkPolicy does - not allow any traffic (and serves solely to ensure that the - pods it selects are isolated by default) - items: - description: NetworkPolicyIngressRule describes a particular - set of traffic that is allowed to the pods matched by a - NetworkPolicySpec's podSelector. The traffic must match - both ports and from. - properties: - from: - description: List of sources which should be able to access - the pods selected for this rule. Items in this list - are combined using a logical OR operation. If this field - is empty or missing, this rule matches all sources (traffic - not restricted by source). If this field is present - and contains at least one item, this rule allows traffic - only if the traffic matches at least one item in the - from list. - items: - description: NetworkPolicyPeer describes a peer to allow - traffic to/from. Only certain combinations of fields - are allowed - properties: - ipBlock: - description: IPBlock defines policy on a particular - IPBlock. If this field is set then neither of - the other fields can be. - properties: - cidr: - description: CIDR is a string representing the - IP Block Valid examples are "192.168.1.1/24" - or "2001:db9::/64" - type: string - except: - description: Except is a slice of CIDRs that - should not be included within an IP Block - Valid examples are "192.168.1.1/24" or "2001:db9::/64" - Except values will be rejected if they are - outside the CIDR range - items: - type: string - type: array - required: - - cidr - type: object - namespaceSelector: - description: "Selects Namespaces using cluster-scoped - labels. This field follows standard label selector - semantics; if present but empty, it selects all - namespaces. \n If PodSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects all - Pods in the Namespaces selected by NamespaceSelector." - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: "This is a label selector which selects - Pods. This field follows standard label selector - semantics; if present but empty, it selects all - pods. \n If NamespaceSelector is also set, then - the NetworkPolicyPeer as a whole selects the Pods - matching PodSelector in the Namespaces selected - by NamespaceSelector. Otherwise it selects the - Pods matching PodSelector in the policy's own - Namespace." - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: A label selector requirement - is a selector that contains values, a key, - and an operator that relates the key and - values. - properties: - key: - description: key is the label key that - the selector applies to. - type: string - operator: - description: operator represents a key's - relationship to a set of values. Valid - operators are In, NotIn, Exists and - DoesNotExist. - type: string - values: - description: values is an array of string - values. If the operator is In or NotIn, - the values array must be non-empty. - If the operator is Exists or DoesNotExist, - the values array must be empty. This - array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} - pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, - whose key field is "key", the operator is - "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - ports: - description: List of ports which should be made accessible - on the pods selected for this rule. Each item in this - list is combined using a logical OR. If this field is - empty or missing, this rule matches all ports (traffic - not restricted by port). If this field is present and - contains at least one item, then this rule allows traffic - only if the traffic matches at least one port in the - list. - items: - description: NetworkPolicyPort describes a port to allow - traffic on - properties: - endPort: - description: If set, indicates that the range of - ports from port to endPort, inclusive, should - be allowed by the policy. This field cannot be - defined if the port field is not defined or if - the port field is defined as a named (string) - port. The endPort must be equal or greater than - port. This feature is in Beta state and is enabled - by default. It can be disabled using the Feature - Gate "NetworkPolicyEndPort". - format: int32 - type: integer - port: + description: Default resource requirement limit value by resource name if resource limit is omitted. + type: object + defaultRequest: + additionalProperties: anyOf: - type: integer - type: string - description: The port on the given protocol. This - can either be a numerical or named port on a pod. - If this field is not provided, this matches all - port names and numbers. If present, only traffic - on the specified protocol AND port will be matched. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - protocol: - default: TCP - description: The protocol (TCP, UDP, or SCTP) which - traffic must match. If not specified, this field - defaults to TCP. - type: string - type: object - type: array - type: object - type: array - podSelector: - description: Selects the pods to which this NetworkPolicy object - applies. The array of ingress rules is applied to any pods - selected by this field. Multiple network policies can select - the same set of pods. In this case, the ingress rules for - each are combined additively. This field is NOT optional and - follows standard label selector semantics. An empty podSelector - matches all pods in this namespace. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, - Exists and DoesNotExist. + description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted. + type: object + max: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Max usage constraints on this kind by resource name. + type: object + maxLimitRequestRatio: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. + type: object + min: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Min usage constraints on this kind by resource name. + type: object + type: + description: Type of resource that this limit applies to. type: string - values: - description: values is an array of string values. - If the operator is In or NotIn, the values array - must be non-empty. If the operator is Exists or - DoesNotExist, the values array must be empty. This - array is replaced during a strategic merge patch. - items: - type: string - type: array required: - - key - - operator + - type type: object type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object + required: + - limits type: object - x-kubernetes-map-type: atomic - policyTypes: - description: List of rule types that the NetworkPolicy relates - to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", - "Egress"]. If this field is not specified, it will default - based on the existence of Ingress or Egress rules; policies - that contain an Egress section are assumed to affect Egress, - and all policies (whether or not they contain an Ingress section) - are assumed to affect Ingress. If you want to write an egress-only - policy, you must explicitly specify policyTypes [ "Egress" - ]. Likewise, if you want to write a policy that specifies - that no egress is allowed, you must specify a policyTypes - value that include "Egress" (since such a policy would not - include an Egress section and would otherwise default to just - [ "Ingress" ]). This field is beta-level in 1.8 - items: - description: PolicyType string describes the NetworkPolicy - type This type is beta-level in 1.8 - type: string - type: array - required: - - podSelector - type: object - type: array - nodeSelector: - additionalProperties: - type: string + type: array type: object - owner: - description: OwnerSpec defines tenant owner name and kind. + namespaceOptions: + description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. properties: - kind: - enum: - - User - - Group - type: string - name: - type: string - required: - - kind - - name - type: object - resourceQuotas: - items: - description: ResourceQuotaSpec defines the desired hard limits to - enforce for Quota. - properties: - hard: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'hard is the set of desired hard limits for each - named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' - type: object - scopeSelector: - description: scopeSelector is also a collection of filters like - scopes that must match each object tracked by a quota but - expressed using ScopeSelectorOperator in combination with - possible values. For a resource to match, both scopes AND - scopeSelector (if specified in spec), must be matched. - properties: - matchExpressions: - description: A list of scope selector requirements by scope - of the resources. - items: - description: A scoped-resource selector requirement is - a selector that contains values, a scope name, and an - operator that relates the scope name and values. - properties: - operator: - description: Represents a scope's relationship to - a set of values. Valid operators are In, NotIn, - Exists, DoesNotExist. - type: string - scopeName: - description: The name of the scope that the selector - applies to. - type: string - values: - description: An array of string values. If the operator - is In or NotIn, the values array must be non-empty. - If the operator is Exists or DoesNotExist, the values - array must be empty. This array is replaced during - a strategic merge patch. - items: - type: string - type: array - required: - - operator - - scopeName - type: object - type: array - type: object - x-kubernetes-map-type: atomic - scopes: - description: A collection of filters that must match each object - tracked by a quota. If not specified, the quota matches all - objects. - items: - description: A ResourceQuotaScope defines a filter that must - match each object tracked by a quota - type: string + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + forbiddenAnnotations: + description: Define the annotations that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + forbiddenLabels: + description: Define the labels that a Tenant Owner cannot set for their Namespace resources. + properties: + denied: + items: + type: string + type: array + deniedRegex: + type: string + type: object + quota: + description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. + format: int32 + minimum: 1 + type: integer + type: object + networkPolicies: + description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. + properties: + items: + items: + description: NetworkPolicySpec provides the specification of a NetworkPolicy + properties: + egress: + description: egress is a list of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8 + items: + description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8 + properties: + ports: + description: ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + to: + description: to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" + type: string + except: + description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + type: object + type: array + ingress: + description: ingress is a list of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default) + items: + description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from. + properties: + from: + description: from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list. + items: + description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed + properties: + ipBlock: + description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. + properties: + cidr: + description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" + type: string + except: + description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range + items: + type: string + type: array + required: + - cidr + type: object + namespaceSelector: + description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace." + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + ports: + description: ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list. + items: + description: NetworkPolicyPort describes a port to allow traffic on + properties: + endPort: + description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched. + x-kubernetes-int-or-string: true + protocol: + default: TCP + description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP. + type: string + type: object + type: array + type: object + type: array + podSelector: + description: podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + description: policyTypes is a list of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of ingress or egress rules; policies that contain an egress section are assumed to affect egress, and all policies (whether or not they contain an ingress section) are assumed to affect ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8 + items: + description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8 + type: string + type: array + required: + - podSelector + type: object + type: array + type: object + nodeSelector: + additionalProperties: + type: string + description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. + type: object + owners: + description: Specifies the owners of the Tenant. Mandatory. + items: + properties: + clusterRoles: + default: + - admin + - capsule-namespace-deleter + description: Defines additional cluster-roles for the specific Owner. + items: + type: string type: array + kind: + description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of tenant owner. + type: string + proxySettings: + description: Proxy settings for tenant owner. + items: + properties: + kind: + enum: + - Nodes + - StorageClasses + - IngressClasses + - PriorityClasses + - RuntimeClasses + - PersistentVolumes + type: string + operations: + items: + enum: + - List + - Update + - Delete + type: string + type: array + required: + - kind + - operations + type: object + type: array + required: + - kind + - name type: object type: array - servicesMetadata: + preventDeletion: + description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined. + type: boolean + priorityClasses: + description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. A default value can be specified, and all the Pod resources created will inherit the declared class. Optional. properties: - additionalAnnotations: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + default: + type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: additionalProperties: type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - additionalLabels: + type: object + x-kubernetes-map-type: atomic + resourceQuotas: + description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. + properties: + items: + items: + description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota. + properties: + hard: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/' + type: object + scopeSelector: + description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched. + properties: + matchExpressions: + description: A list of scope selector requirements by scope of the resources. + items: + description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values. + properties: + operator: + description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector applies to. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + x-kubernetes-map-type: atomic + scopes: + description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + items: + description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota + type: string + type: array + type: object + type: array + scope: + default: Tenant + description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant + enum: + - Tenant + - Namespace + type: string + type: object + runtimeClasses: + description: Specifies the allowed RuntimeClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. Optional. + properties: + allowed: + items: + type: string + type: array + allowedRegex: + type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: additionalProperties: type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + serviceOptions: + description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. + properties: + additionalMetadata: + description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + allowedServices: + description: Block or deny certain type of Services. Optional. + properties: + externalName: + default: true + description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + loadBalancer: + default: true + description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + nodePort: + default: true + description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional. + type: boolean + type: object + externalIPs: + description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. + properties: + allowed: + items: + pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ + type: string + type: array + required: + - allowed type: object type: object storageClasses: + description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class. Optional. properties: allowed: items: @@ -863,21 +821,60 @@ spec: type: array allowedRegex: type: string + default: + type: string + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object + x-kubernetes-map-type: atomic required: - - owner + - owners type: object status: - description: TenantStatus defines the observed state of Tenant. + description: Returns the observed state of the Tenant. properties: namespaces: + description: List of namespaces assigned to the Tenant. items: type: string type: array size: + description: How many namespaces are assigned to the Tenant. type: integer + state: + default: Active + description: The operational state of the Tenant. Possible values are "Active", "Cordoned". + enum: + - Cordoned + - Active + type: string required: - size + - state type: object type: object served: true From 7becdbaf799582426e4b5c5bc06753e7ae867a7b Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 25 May 2023 14:08:13 +0200 Subject: [PATCH 145/153] docs(crds): updated descriptions --- docs/content/general/crds-apis.md | 234 +++++++++++++++--------------- 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/docs/content/general/crds-apis.md b/docs/content/general/crds-apis.md index 395e7060..696a41eb 100644 --- a/docs/content/general/crds-apis.md +++ b/docs/content/general/crds-apis.md @@ -675,28 +675,28 @@ NetworkPolicySpec provides the specification of a NetworkPolicy @@ -707,7 +707,7 @@ NetworkPolicySpec provides the specification of a NetworkPolicy -Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. +podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
annotationsmap[string]stringdenied[]string
false
labelsmap[string]stringdeniedRegexstring


- Enum: Nodes, StorageClasses, IngressClasses, PriorityClasses
+ Enum: Nodes, StorageClasses, IngressClasses, PriorityClasses, RuntimeClasses, PersistentVolumes
true
podSelector object - Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
+ podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
true
egress []object - List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
+ egress is a list of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
false
ingress []object - List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
+ ingress is a list of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
false
policyTypes []string - List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
+ policyTypes is a list of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of ingress or egress rules; policies that contain an egress section are assumed to affect egress, and all policies (whether or not they contain an ingress section) are assumed to affect ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
false
@@ -795,14 +795,14 @@ NetworkPolicyEgressRule describes a particular set of traffic that is allowed ou @@ -828,7 +828,7 @@ NetworkPolicyPort describes a port to allow traffic on @@ -837,14 +837,14 @@ NetworkPolicyPort describes a port to allow traffic on @@ -872,23 +872,23 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin @@ -899,7 +899,7 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin -IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. +ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
ports []object - List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
+ ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
false
to []object - List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
+ to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
false
endPort integer - If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
+ endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.

Format: int32
port int or string - The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
+ port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
false
protocol string - The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.

Default: TCP
ipBlock object - IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
false
namespaceSelector object - Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
false
podSelector object - This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
false
@@ -914,14 +914,14 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe @@ -932,8 +932,8 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe -Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. +namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
cidr string - CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
true
except []string - Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
false
@@ -1006,8 +1006,8 @@ A label selector requirement is a selector that contains values, a key, and an o -This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. +podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
@@ -1095,14 +1095,14 @@ NetworkPolicyIngressRule describes a particular set of traffic that is allowed t @@ -1128,23 +1128,23 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin @@ -1155,7 +1155,7 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin -IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. +ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
from []object - List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
+ from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
false
ports []object - List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
+ ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
false
ipBlock object - IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
false
namespaceSelector object - Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
false
podSelector object - This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
false
@@ -1170,14 +1170,14 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe @@ -1188,8 +1188,8 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe -Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. +namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
cidr string - CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
true
except []string - Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
false
@@ -1262,8 +1262,8 @@ A label selector requirement is a selector that contains values, a key, and an o -This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. +podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
@@ -1351,7 +1351,7 @@ NetworkPolicyPort describes a port to allow traffic on @@ -1360,14 +1360,14 @@ NetworkPolicyPort describes a port to allow traffic on @@ -3696,28 +3696,28 @@ NetworkPolicySpec provides the specification of a NetworkPolicy @@ -3728,7 +3728,7 @@ NetworkPolicySpec provides the specification of a NetworkPolicy -Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. +podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
endPort integer - If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
+ endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.

Format: int32
port int or string - The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
+ port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
false
protocol string - The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.

Default: TCP
podSelector object - Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
+ podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
true
egress []object - List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
+ egress is a list of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
false
ingress []object - List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
+ ingress is a list of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
false
policyTypes []string - List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
+ policyTypes is a list of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of ingress or egress rules; policies that contain an egress section are assumed to affect egress, and all policies (whether or not they contain an ingress section) are assumed to affect ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
false
@@ -3816,14 +3816,14 @@ NetworkPolicyEgressRule describes a particular set of traffic that is allowed ou @@ -3849,7 +3849,7 @@ NetworkPolicyPort describes a port to allow traffic on @@ -3858,14 +3858,14 @@ NetworkPolicyPort describes a port to allow traffic on @@ -3893,23 +3893,23 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin @@ -3920,7 +3920,7 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin -IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. +ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
ports []object - List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
+ ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
false
to []object - List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
+ to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
false
endPort integer - If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
+ endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.

Format: int32
port int or string - The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
+ port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
false
protocol string - The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.

Default: TCP
ipBlock object - IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
false
namespaceSelector object - Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
false
podSelector object - This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
false
@@ -3935,14 +3935,14 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe @@ -3953,8 +3953,8 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe -Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. +namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
cidr string - CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
true
except []string - Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
false
@@ -4027,8 +4027,8 @@ A label selector requirement is a selector that contains values, a key, and an o -This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. +podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
@@ -4116,14 +4116,14 @@ NetworkPolicyIngressRule describes a particular set of traffic that is allowed t @@ -4149,23 +4149,23 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin @@ -4176,7 +4176,7 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin -IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. +ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
from []object - List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
+ from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
false
ports []object - List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
+ ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
false
ipBlock object - IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
false
namespaceSelector object - Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
false
podSelector object - This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
false
@@ -4191,14 +4191,14 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe @@ -4209,8 +4209,8 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe -Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. +namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
cidr string - CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
true
except []string - Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
false
@@ -4283,8 +4283,8 @@ A label selector requirement is a selector that contains values, a key, and an o -This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. +podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
@@ -4372,7 +4372,7 @@ NetworkPolicyPort describes a port to allow traffic on @@ -4381,14 +4381,14 @@ NetworkPolicyPort describes a port to allow traffic on @@ -5703,28 +5703,28 @@ NetworkPolicySpec provides the specification of a NetworkPolicy @@ -5735,7 +5735,7 @@ NetworkPolicySpec provides the specification of a NetworkPolicy -Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace. +podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
endPort integer - If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
+ endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.

Format: int32
port int or string - The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
+ port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
false
protocol string - The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.

Default: TCP
podSelector object - Selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
+ podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
true
egress []object - List of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
+ egress is a list of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
false
ingress []object - List of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
+ ingress is a list of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
false
policyTypes []string - List of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an Egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
+ policyTypes is a list of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of ingress or egress rules; policies that contain an egress section are assumed to affect egress, and all policies (whether or not they contain an ingress section) are assumed to affect ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
false
@@ -5823,14 +5823,14 @@ NetworkPolicyEgressRule describes a particular set of traffic that is allowed ou @@ -5856,7 +5856,7 @@ NetworkPolicyPort describes a port to allow traffic on @@ -5865,14 +5865,14 @@ NetworkPolicyPort describes a port to allow traffic on @@ -5900,23 +5900,23 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin @@ -5927,7 +5927,7 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin -IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. +ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
ports []object - List of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
+ ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
false
to []object - List of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
+ to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
false
endPort integer - If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
+ endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.

Format: int32
port int or string - The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
+ port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
false
protocol string - The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.

Default: TCP
ipBlock object - IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
false
namespaceSelector object - Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
false
podSelector object - This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
false
@@ -5942,14 +5942,14 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe @@ -5960,8 +5960,8 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe -Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. +namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
cidr string - CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
true
except []string - Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
false
@@ -6034,8 +6034,8 @@ A label selector requirement is a selector that contains values, a key, and an o -This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. +podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
@@ -6123,14 +6123,14 @@ NetworkPolicyIngressRule describes a particular set of traffic that is allowed t @@ -6156,23 +6156,23 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin @@ -6183,7 +6183,7 @@ NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combin -IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be. +ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
from []object - List of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
+ from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
false
ports []object - List of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
+ ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
false
ipBlock object - IPBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
+ ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
false
namespaceSelector object - Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector.
+ namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
false
podSelector object - This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace.
+ podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
false
@@ -6198,14 +6198,14 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe @@ -6216,8 +6216,8 @@ IPBlock defines policy on a particular IPBlock. If this field is set then neithe -Selects Namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. - If PodSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. +namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. + If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector.
cidr string - CIDR is a string representing the IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64"
+ cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
true
except []string - Except is a slice of CIDRs that should not be included within an IP Block Valid examples are "192.168.1.1/24" or "2001:db9::/64" Except values will be rejected if they are outside the CIDR range
+ except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
false
@@ -6290,8 +6290,8 @@ A label selector requirement is a selector that contains values, a key, and an o -This is a label selector which selects Pods. This field follows standard label selector semantics; if present but empty, it selects all pods. - If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. +podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. + If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace.
@@ -6379,7 +6379,7 @@ NetworkPolicyPort describes a port to allow traffic on @@ -6388,14 +6388,14 @@ NetworkPolicyPort describes a port to allow traffic on From 5ca175416f6b6c49377e27b582df6779947669b7 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 1 Jun 2023 18:13:53 +0200 Subject: [PATCH 146/153] chore(kustomize): releasing v0.3.2 --- config/install.yaml | 2 +- config/manager/kustomization.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/install.yaml b/config/install.yaml index 2470c90a..837c91ee 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -2769,7 +2769,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: clastix/capsule:v0.3.1 + image: clastix/capsule:v0.3.2 imagePullPolicy: IfNotPresent name: manager ports: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 1f8911a3..5f5ea670 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -7,4 +7,4 @@ kind: Kustomization images: - name: controller newName: clastix/capsule - newTag: v0.3.1 + newTag: v0.3.2 From 4be0cdc659833579e7575df865773d20db03f803 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Thu, 1 Jun 2023 18:14:02 +0200 Subject: [PATCH 147/153] chore(helm): releasing v0.3.2 --- charts/capsule/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index e57f6fbb..82091176 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,8 +21,8 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.4.2 +version: 0.4.3 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. -appVersion: 0.3.1 +appVersion: 0.3.2 From 6cd62d9e91793bd001cbd762dda849fc60edf53f Mon Sep 17 00:00:00 2001 From: Max Fedotov Date: Mon, 26 Jun 2023 15:30:15 +0300 Subject: [PATCH 148/153] fix(helm): remove hardcoded capsule-system namespace for tenantresource-objects webhook --- charts/capsule/Chart.yaml | 2 +- charts/capsule/templates/validatingwebhookconfiguration.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 82091176..817d8d67 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,7 +21,7 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.4.3 +version: 0.4.4 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. diff --git a/charts/capsule/templates/validatingwebhookconfiguration.yaml b/charts/capsule/templates/validatingwebhookconfiguration.yaml index 58518ac0..aa4101a9 100644 --- a/charts/capsule/templates/validatingwebhookconfiguration.yaml +++ b/charts/capsule/templates/validatingwebhookconfiguration.yaml @@ -265,7 +265,7 @@ webhooks: {{- end }} service: name: capsule-webhook-service - namespace: capsule-system + namespace: {{ .Release.Namespace }} path: /tenantresource-objects failurePolicy: {{ .Values.webhooks.tenantResourceObjects.failurePolicy }} name: resource-objects.tenant.capsule.clastix.io From 45ad56c586cab5e8409208dec8f93278d38a5a31 Mon Sep 17 00:00:00 2001 From: Max Fedotov Date: Tue, 27 Jun 2023 16:08:36 +0300 Subject: [PATCH 149/153] fix: remove ownerReferences from tenantResource namespacedItems before applying them --- controllers/resources/processor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/controllers/resources/processor.go b/controllers/resources/processor.go index 0ebd5b71..20075bb9 100644 --- a/controllers/resources/processor.go +++ b/controllers/resources/processor.go @@ -172,6 +172,7 @@ func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant for _, o := range objs.Items { obj := o obj.SetNamespace(ns.Name) + obj.SetOwnerReferences(nil) multiErr.Go(func() error { kv := keysAndValues From 51b23d16dcefa1efa9039961dc82760e77ffc0d7 Mon Sep 17 00:00:00 2001 From: Max Fedotov Date: Tue, 27 Jun 2023 17:21:54 +0300 Subject: [PATCH 150/153] fix: incorrect capsule label comparison in PVC webhook --- pkg/webhook/pvc/pv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/webhook/pvc/pv.go b/pkg/webhook/pvc/pv.go index 06d65791..6f004d25 100644 --- a/pkg/webhook/pvc/pv.go +++ b/pkg/webhook/pvc/pv.go @@ -77,7 +77,7 @@ func (p PV) OnCreate(client client.Client, decoder *admission.Decoder, recorder return utils.ErroredResponse(NewMissingTenantPVLabelsError(pv.GetName())) } - if value != p.capsuleLabel { + if value != tnt.Name { return utils.ErroredResponse(NewCrossTenantPVMountError(pv.GetName())) } From e6074a86c0b5db125c5931a7a6b465524d182644 Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Fri, 23 Jun 2023 22:00:10 +0200 Subject: [PATCH 151/153] build(go): upgrading to 1.19.10 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f08a6738..9a47d3d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.19 as builder +FROM golang:1.19.10 as builder WORKDIR /workspace # Copy the Go Modules manifests From d2dd055818677161e1752b34586a1ec609fdfa4d Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 27 Jun 2023 19:12:15 +0200 Subject: [PATCH 152/153] chore(kustomize): releasing v0.3.3 --- config/install.yaml | 2 +- config/manager/kustomization.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/install.yaml b/config/install.yaml index 837c91ee..dc3feea3 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -2769,7 +2769,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: clastix/capsule:v0.3.2 + image: clastix/capsule:v0.3.3 imagePullPolicy: IfNotPresent name: manager ports: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 5f5ea670..5274894d 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -7,4 +7,4 @@ kind: Kustomization images: - name: controller newName: clastix/capsule - newTag: v0.3.2 + newTag: v0.3.3 From 64513b8dee332d119b1a5a3a4c267f84ff2c7ecb Mon Sep 17 00:00:00 2001 From: Dario Tranchitella Date: Tue, 27 Jun 2023 19:12:25 +0200 Subject: [PATCH 153/153] chore(helm): releasing v0.3.3 --- charts/capsule/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/capsule/Chart.yaml b/charts/capsule/Chart.yaml index 817d8d67..27e1c9cb 100644 --- a/charts/capsule/Chart.yaml +++ b/charts/capsule/Chart.yaml @@ -21,8 +21,8 @@ sources: # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. -version: 0.4.4 +version: 0.4.5 # This is the version number of the application being deployed. # This version number should be incremented each time you make changes to the application. -appVersion: 0.3.2 +appVersion: 0.3.3
endPort integer - If set, indicates that the range of ports from port to endPort, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port. This feature is in Beta state and is enabled by default. It can be disabled using the Feature Gate "NetworkPolicyEndPort".
+ endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.

Format: int32
port int or string - The port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
+ port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
false
protocol string - The protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
+ protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.

Default: TCP