From bdaca353844783bb87a927efbd533e36e559a267 Mon Sep 17 00:00:00 2001 From: battlebyte Date: Fri, 6 Oct 2023 16:34:12 +0200 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 6f67f8031f356d141d39d4a1e4d8e5f17209074a Author: battlebyte Date: Wed Sep 20 11:47:51 2023 +0200 Added JSON output, annotation output. Tests and bug fix for consumer groups Lint fixes commit 996bb8441fd1a21acde0e98bd9ff9ec6cbbe5e1d Merge: 227f6b3 5b73a99 Author: battlebyte Date: Tue Sep 12 14:52:47 2023 +0200 Merge branch 'main' of github.com:battlebyte/deck commit 227f6b389aa8cd8d183e73bcd38cc24967346d84 Author: battlebyte Date: Tue Sep 12 14:52:19 2023 +0200 Fix MTLS transformation. commit 5b73a99c9170a15e6561a0d68caf5bd784a918a0 Merge: 97d1626 b193a18 Author: Jordi Fernandez Date: Tue Sep 12 10:36:01 2023 +0200 Merge branch 'Kong:main' into main commit 97d162687af6a04569bbde5b147ca07e5bd1a6f0 Author: battlebyte Date: Tue Sep 12 10:34:55 2023 +0200 More tests. Minor bug fixes. commit b193a1801e4523ff7676120278ebdcbc7176f9b9 Author: Gabriele Gerbino Date: Thu Sep 7 17:51:38 2023 +0200 chore: bump changelog for v1.26.1 commit 444876b0809798f9aac7a8053416a7445a7fc96a Author: Gabriele Gerbino Date: Wed Aug 30 18:04:37 2023 +0200 fix: raise error if files have different Runtime Groups commit 55fb1151c24ca5e0b3c69e29b5d6c659370e88f8 Author: Gabriele Date: Wed Sep 6 11:42:22 2023 +0200 fix: correct consumers validation when custom_id is used (#1012) commit 02db4460ef18009e96eef29690d3a59eb83c3fac Author: lena-larionova <54370747+lena-larionova@users.noreply.github.com> Date: Wed Aug 30 09:47:01 2023 -0700 fix: change go-apiops in examples to deck file commit 74983584b59df9e67d8b1ca7c0683d3c8f8c1908 Author: battlebyte Date: Tue Sep 5 12:22:44 2023 +0200 Update call to file.GetContentFromFiles commit 88f3c05dcc41a572c138c0d5d8c484deadfda78e Merge: 2189389 20ef81e Author: battlebyte Date: Tue Sep 5 12:13:00 2023 +0200 Merge branch 'main' of github.com:battlebyte/deck commit 2189389e60a542bce6fbebc95008d5bb2a9353e5 Author: battlebyte Date: Tue Sep 5 11:51:01 2023 +0200 New command "deck file kong2kic" commit a6021d90fd91efd55a2c0d0cbae0a0357b2ba76e Author: battlebyte Date: Tue Sep 5 11:49:14 2023 +0200 New command "deck file kong2kic". commit d40b3cb4616e2578d198bcf17aec09cc1e5116ed Author: battlebyte Date: Mon Aug 28 09:20:46 2023 +0200 check plugin.name is not nil commit bbe65759906999fe396d04185fd941255d35b582 Merge: 664b305 b2e6218 Author: Jordi Fernandez Date: Mon Aug 28 09:19:23 2023 +0200 Merge pull request #1 from mod42/main crashed because by use of InstanceName instead of Name commit 20ef81eb32b3de4023ea815d3db02dabf722fa02 Author: lena-larionova <54370747+lena-larionova@users.noreply.github.com> Date: Fri Aug 11 12:11:53 2023 -0700 fix: remove errant comma from CLI example commit b2e62189a62353d911d6d277d8abdf8510e0346f Author: mod42 Date: Fri Aug 11 18:11:24 2023 +0200 crashed because by use of InstanceName instead of Name commit 574122f31dfcb1ec03d93cf2eef503d620b6b215 Author: Gabriele Gerbino Date: Thu Aug 10 17:11:53 2023 +0200 fix: set default strip_path to true In the past decK had to set the default value of the `strip_path` field to [false](https://github.com/Kong/deck/blob/v1.26.0/utils/constants.go#L22) because of some limitation in the library used for defaults injection: this was a workaround to allow this field to be set to `false`. Then, if the field was not set in the configuration, the field was then turned into `true` at runtime via inspection of the Kong Admin API. This commit changes the decK default for the `strip_path` field to `true`, since now the underlying library has fixed the issue that triggered this workaround in the first place. commit 63b9742587ab3a81ece193e69418752f59bf3e82 Author: Gabriele Gerbino Date: Wed Aug 9 18:37:15 2023 +0200 chore: bump changelog for v1.26.0 commit ea2ab928da7ad13be2d3d66fafd9b67c2baecb84 Author: Gabriele Date: Wed Aug 9 18:29:46 2023 +0200 feat: enable Consumer Groups scoped Plugins with Konnect (#994) commit 10c8698399a21da38ca1b190f173d757d260e8eb Author: Gabriele Date: Wed Aug 9 16:10:42 2023 +0200 fix: remove old konnect authentication fallback (#995) decK used to require to work with both "old" and "new" Konnect. This requirement is now obsolete, since the "old" Konnect doesn't exist anymore, so it is the need of this fallback mechanism. This commit removes such mechanism. commit d11d04dd2ec366f2b11dd56a2fe29bfe0031a263 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Aug 9 04:06:18 2023 +0000 chore(deps): bump golang from 1.20.7 to 1.21.0 Bumps golang from 1.20.7 to 1.21.0. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit b248a0f86eeb6d7c073b6777a93524c7d191e29d Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Aug 8 04:48:25 2023 +0000 chore(deps): bump alpine from 3.18.2 to 3.18.3 Bumps alpine from 3.18.2 to 3.18.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit 328ae943624a09706e2994cf6f49e1852dd52f46 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Jul 20 04:57:41 2023 +0000 chore(deps): bump k8s.io/code-generator from 0.27.3 to 0.27.4 Bumps [k8s.io/code-generator](https://github.com/kubernetes/code-generator) from 0.27.3 to 0.27.4. - [Commits](https://github.com/kubernetes/code-generator/compare/v0.27.3...v0.27.4) --- updated-dependencies: - dependency-name: k8s.io/code-generator dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit a78470021b86970216fc87c12a2805935ba625c0 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri Aug 4 14:07:11 2023 +0000 chore(deps): bump github.com/shirou/gopsutil/v3 from 3.23.6 to 3.23.7 Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.6 to 3.23.7. - [Release notes](https://github.com/shirou/gopsutil/releases) - [Commits](https://github.com/shirou/gopsutil/compare/v3.23.6...v3.23.7) --- updated-dependencies: - dependency-name: github.com/shirou/gopsutil/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit 36b6a3cde8b68f5fea3c1adaeec55d1850e647a5 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Aug 2 04:03:55 2023 +0000 chore(deps): bump golang from 1.20.6 to 1.20.7 Bumps golang from 1.20.6 to 1.20.7. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit c8234139284af7018a336cc7150484d2ecedca12 Author: Patryk Małek Date: Fri Aug 4 16:38:28 2023 +0200 tests: use testing helpers for env and cleanup (#992) commit af7eaa2d568e08f923f1016461f5ca1948308b32 Author: Gabriele Date: Fri Aug 4 16:05:45 2023 +0200 feat: support scoping plugins to consumer-groups commit 3e4196dc890ee20a245bc0db770f33372ce31d6e Author: Patryk Małek Date: Wed Aug 2 13:04:57 2023 +0200 chore: mark file/kong_json_schema.json as generated code commit 664b305e5197217153f6bfd14f981bf32db7f87d Author: battlebyte Date: Tue Aug 1 15:56:14 2023 +0200 fix test commit 60752815e30333a5e3a89dd6cfc0ebccb3840e55 Author: battlebyte Date: Tue Aug 1 15:51:30 2023 +0200 convert command kong gateway to ingress controller commit 37196ea68c05e836f3ece5bbbbc7d615af605943 Author: lena-larionova <54370747+lena-larionova@users.noreply.github.com> Date: Fri Jul 28 14:40:23 2023 -0700 fix: typo in global konnect-token-file flag commit 86497a5969b4b49929d1beaafaa27d5b61364830 Author: Gabriele Date: Fri Jul 28 18:01:21 2023 +0200 fix: use same API to pull Consumer Groups from Konnect and Kong (#984) commit 6342b2b9966d99ec4ddf9044af26c7c2081c2505 Author: Gabriele Gerbino Date: Thu Jul 27 11:32:57 2023 +0200 fix: correct consumers handling when custom_id is used Right now, when a consumer has the `username` equal to the `custom_id` of another consumer, decK fails because of the way the consumers are handled in the internal in-memory DB. ``` _format_version: "3.0" consumers: - custom_id: foo id: ce49186d-7670-445d-a218-897631b29ada username: Foo - custom_id: bar id: 7820f383-7b77-4fcc-af7f-14ff3e256693 username: foo ``` ``` $ deck sync Error: inserting consumer into state: inserting consumer Foo: entity already exists ``` This commit fixes this defect by modifying the way consumers are looked up in the in-memory DB. commit d7345148fd7f175aa58208611be35ec429108f55 Author: Thijs Schreijer Date: Thu Jul 27 16:34:17 2023 +0200 feat(render): add new command to render final file (#963) Co-authored-by: Vincent Le Goff commit aee76ddf51234129940302e38061eaf58348523a Author: Gabriele Date: Thu Jul 27 13:30:28 2023 +0200 fix: correct diffs when comparing objects with empty tags (#985) Right now, when some declarative configuration having empty tags on any of its entities is deployed, subsequent `diff` / `sync` will show misleading results: ``` $ cat kong.yaml _format_version: "3.0" services: - name: foo protocol: http host: foo port: 8000 tags: [] $ deck sync creating service foo Summary: Created: 1 Updated: 0 Deleted: 0 $ deck sync updating service foo { "connect_timeout": 60000, "enabled": true, "host": "foo", "id": "afadd2ed-2bcd-49fc-9000-adb58cd11b50", "name": "foo", "port": 8000, "protocol": "http", "read_timeout": 60000, "retries": 5, "write_timeout": 60000 } Summary: Created: 0 Updated: 1 Deleted: 0 ``` The second `deck sync` run is showing an update without an actual diff. This is due to the way empty tags are treated internally in decK. This commit fixes this defect. commit b6fdeb25409842e559d9ca91df82fc397601cc4c Merge: 5ac84d4 b1fde70 Author: battlebyte Date: Wed Jul 26 15:30:50 2023 +0200 Merge remote-tracking branch 'origin/main' commit 24ef3727f3cc2ae49c17b4967e7a49b7a6698373 Author: Thijs Schreijer Date: Wed Jul 26 13:37:31 2023 +0200 fix(ci): set the codecov token to reduce flakiness (#983) commit d3f0a676b7b22505bf2c4b9063f60ef95dab04f8 Author: Gabriele Gerbino Date: Mon Jul 24 16:25:01 2023 +0200 chore: update release date for v1.24.0 commit b1fde70f050cb914c3c30e39dc65b2294f43fe9d Author: Gabriele Gerbino Date: Wed Jul 19 17:46:59 2023 +0200 chore: update changelog with missing changes for 1.24.0 commit ab90fbbf5042d6387f3a251673f9571ff4e74713 Author: Gabriele Gerbino Date: Wed Jul 19 14:48:05 2023 +0200 tests: restrict diff test scope to avoid failure due to schema changes commit e337551d3e70004379f952eb3a91ff5af1deb5ec Author: Grzegorz Burzyński Date: Wed Jul 19 13:44:18 2023 +0200 feat: handle certificates:snis relation for Konnect commit 237adbb0c6d8d4c7228aa20877cdeb8e9e1f1646 Author: Gabriele Gerbino Date: Wed Jul 19 07:19:23 2023 +0200 chore: update changelog for 1.24.0 commit fa6bd5fc98f5ad3c784c3621a6b9dd70908597d5 Author: Gabriele Gerbino Date: Tue Jul 18 21:47:30 2023 +0200 feat: improve error logs when running sync/diff/validate Currently decK doesn't return very useful and descriptive errors when configuration files are validated. This is particularly painful when the configuration is distributed in several files and the error doesn't specificy which file contains the error. Let's take for example the following files: ``` $ cat services.yaml services: - name: svc1 host: mockbin.org foo: bar consumers: - username: good - name: bad ``` If we run decK, it will return some obscure and incomplete errors: ``` $ deck sync -s services.yaml -s consumers.yaml Error: reading file: validating file content: 1 errors occurred: services.0: Additional property foo is not allowed ``` This commit is going to improve the way decK handles configuration validation error logging in order to provide a more useful description of the problems. When running decK with the new functionality, the following will be returned: ``` $ ./deck sync -s services.yaml -s consumers.yaml Error: 2 errors occurred: reading file services.yaml: validating file content: 1 errors occurred: validation error: object="bar", err=services.0: Additional property foo is not allowed reading file consumers.yaml: validating file content: 3 errors occurred: validation error: object={"name":"bad"}, err=consumers.1: Must validate at least one schema (anyOf) validation error: object={"name":"bad"}, err=consumers.1: username is required validation error: object="bad", err=consumers.1: Additional property name is not allowed ``` commit 70ae4dc37c3c335a5d0085370ee6ad9f0f62947a Author: Thijs Schreijer Date: Wed Jul 19 14:37:27 2023 +0200 chore(deps): bump go-apiops to v0.1.20 (#977) commit 73526090557bf6f1bd6afa8b07945aaa9a54af56 Author: DanielRailean Date: Wed Jul 19 11:50:00 2023 +0200 feat: add option to enable JSON output for diff/sync (#798) commit d3ceb4b5f0c94fd36cdb140ab0cd4fe774f659aa Author: Thijs Schreijer Date: Tue Jul 18 20:53:24 2023 +0200 revert(convert): remove convert from file subcommand (#975) commit d19b622b8de8497fb04efc04e67eebba31423b41 Author: Thijs Schreijer Date: Tue Jul 18 20:28:17 2023 +0200 feat(patch): append to array entries (#974) commit 5d19fe6fbc8ac893bb54d6785149fb20e908cbdc Author: Thijs Schreijer Date: Tue Jul 18 20:05:43 2023 +0200 fix(cli): fix indentation in file-patch cli command (#972) commit 3de08747401e7d8d44dc1ec6e079868e4b1efd87 Author: Gabriele Date: Tue Jul 18 19:49:00 2023 +0200 chore: add 3.3 to test matrix (#973) commit e4a674c948caf560b5af183a6619b5d0861c8f6e Author: Thijs Schreijer Date: Tue Jul 18 13:17:24 2023 +0200 chore(deps): bump go-apiops to 0.1.18 (#970) commit 73228891839e8e1412fa4a39c12a968a6e5e1b38 Author: Thijs Schreijer Date: Tue Jul 18 13:17:08 2023 +0200 chore(validate): remove validate again from file subcommand (#971) commit 9aa351b1ebb9d6675830754e33655659e2acbcab Author: lena-larionova <54370747+lena-larionova@users.noreply.github.com> Date: Tue Jul 18 00:32:51 2023 -0700 docs(apiops): Clean up command text for new file commands (#968) commit 4926a01d571e3021e7c269bcccf5abfc58ff3e15 Author: Thijs Schreijer Date: Mon Jul 17 22:49:45 2023 +0200 fix(cli): oas2kong would not parse the --format flag (#966) commit 95bc95f3b5c610e48fe7e9202a8a7762472c65cc Author: Thijs Schreijer Date: Thu Jul 13 15:48:54 2023 +0200 chore(cli): move 'convert' command under 'file' sub-command commit c79ba726c7943b566029931c2690427e438d40fd Author: Thijs Schreijer Date: Mon Jun 12 15:53:01 2023 +0200 feat(apiops): integrate go-apiops commit 5ac84d46e38262e85778ee24bd60738be048323e Merge: 0781948 5bbd6a8 Author: battlebyte Date: Wed Jul 12 12:02:28 2023 +0200 Merge remote-tracking branch 'origin/main' commit 5bbd6a8d260849a986c93693037b55aa54a2eafc Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Jul 12 04:20:53 2023 +0000 chore(deps): bump golang from 1.20.5 to 1.20.6 Bumps golang from 1.20.5 to 1.20.6. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit 4959e75793f69bcc934c6eb6730f18397db5f1a3 Author: Gabriele Gerbino Date: Wed Jul 5 19:02:42 2023 +0200 chore: use EE script to setup 'validate-kong-release' job commit a18a9efa84d4b4d5926f80cde133a1ac0f14908e Author: Fero <6863207+mikefero@users.noreply.github.com> Date: Wed Jul 5 12:52:31 2023 -0400 chore: add license for Kong Gateway release validation This commit adds the license to allow for enterprise testing and also shortens the run name to make it more readable via the actions tab. commit a1cbe89c45336db3ab9504efbb0e1d0f18002392 Author: Fero <6863207+mikefero@users.noreply.github.com> Date: Wed Jul 5 12:40:38 2023 -0400 chore: add a GitHub action for validating Kong Gateway images This commit adds a workflow with two inputs to supply the Kong Gateway image and decK branch to test against. commit 508f7d7235e9f2a263e57b03e453fdecc006b841 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Jun 22 19:29:56 2023 +0000 chore(deps): bump github.com/hashicorp/go-retryablehttp Bumps [github.com/hashicorp/go-retryablehttp](https://github.com/hashicorp/go-retryablehttp) from 0.7.2 to 0.7.4. - [Changelog](https://github.com/hashicorp/go-retryablehttp/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/go-retryablehttp/compare/v0.7.2...v0.7.4) --- updated-dependencies: - dependency-name: github.com/hashicorp/go-retryablehttp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit 25a6621eac874126fefdd62153d8ea88d8e3abbc Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Jul 3 04:13:19 2023 +0000 chore(deps): bump github.com/shirou/gopsutil/v3 from 3.23.4 to 3.23.6 Bumps [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) from 3.23.4 to 3.23.6. - [Release notes](https://github.com/shirou/gopsutil/releases) - [Commits](https://github.com/shirou/gopsutil/compare/v3.23.4...v3.23.6) --- updated-dependencies: - dependency-name: github.com/shirou/gopsutil/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit 1afa98548211ae623114805451f1642e7d13af5d Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Jun 22 19:30:00 2023 +0000 chore(deps): bump golang.org/x/sync from 0.2.0 to 0.3.0 Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.2.0 to 0.3.0. - [Commits](https://github.com/golang/sync/compare/v0.2.0...v0.3.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit fd2178112d252079e4076aee6789e3bd034d5352 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Jun 14 04:57:43 2023 +0000 chore(deps): bump docker/metadata-action from 4.4.0 to 4.6.0 Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.4.0 to 4.6.0. - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/v4.4.0...v4.6.0) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit 01c8be93f2a77db580c9ebcc2e669d33f2987467 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Jun 13 04:57:37 2023 +0000 chore(deps): bump golangci/golangci-lint-action from 3.4.0 to 3.6.0 Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.4.0 to 3.6.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.4.0...v3.6.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit 69aa8929dd2be2f371429c22575374f456177842 Author: Gabriele Gerbino Date: Mon Jul 3 15:42:53 2023 +0200 chore: update changelog for 1.23.0 commit 3555a9a0bbcea897da260118620c54c17b1635ff Author: Thijs Schreijer Date: Mon Jul 3 15:33:50 2023 +0200 feat(proxy): honor the proxy environment variables (#952) Co-authored-by: Gabriele Gerbino commit 078194833c9a0c36dc9ab381fda37c84115c3901 Author: battlebyte Date: Fri Jun 30 17:07:12 2023 +0200 Marshal to yaml. Tests expected in files. commit 3e7faee8cf5a66534f43012f53c305d9c03b2636 Author: battlebyte Date: Fri Jun 30 10:58:55 2023 +0200 Remove comments commit e940db0e26c967538a036959e96c9241f66ebc05 Author: battlebyte Date: Thu Jun 29 14:50:16 2023 +0200 decK to kubernetes manifests implementation. --- .DS_Store | Bin 0 -> 6148 bytes .gitattributes | 1 + .github/workflows/integration-enterprise.yaml | 1 + .github/workflows/integration.yaml | 1 + .github/workflows/release.yaml | 2 +- .github/workflows/test.yaml | 3 +- .github/workflows/validate-kong-release.yaml | 39 + .gitignore | 6 + .vscode/launch.json | 7 + CHANGELOG.md | 109 + Dockerfile | 4 +- Makefile | 4 +- cmd/common.go | 93 +- cmd/common_konnect.go | 87 +- cmd/convert.go | 96 - cmd/diff.go | 5 +- cmd/file.go | 15 + cmd/file_addplugins.go | 161 ++ cmd/file_addtags.go | 89 + cmd/file_convert.go | 119 ++ cmd/file_kong2kic.go | 89 + cmd/file_listtags.go | 94 + cmd/file_merge.go | 70 + cmd/file_openapi2kong.go | 89 + cmd/file_patch.go | 177 ++ cmd/file_removetags.go | 112 + cmd/file_render.go | 50 + cmd/konnect_diff.go | 3 - cmd/konnect_dump.go | 3 - cmd/konnect_ping.go | 3 - cmd/konnect_sync.go | 3 - cmd/ping.go | 4 +- cmd/reset.go | 13 +- cmd/root.go | 24 +- cmd/sync.go | 5 +- cmd/validate.go | 7 +- convert/.gitignore | 1 + convert/convert.go | 89 +- convert/convert_test.go | 95 +- convert/testdata/3/output.yaml | 10 - convert/testdata/4/output.yaml | 10 - convert/testdata/5/input.yaml | 35 + convert/testdata/5/output-expected.yaml | 65 + convert/testdata/6/input.yaml | 41 + convert/testdata/6/output-expected.yaml | 62 + convert/testdata/7/input-1.yaml | 25 + convert/testdata/7/input-2.yaml | 12 + convert/testdata/7/output-expected.yaml | 65 + convert/testdata/8/input.yaml | 9 + convert/testdata/8/output-expected.yaml | 13 + convert/testdata/9/input.yaml | 9 + convert/testdata/9/output-expected.yaml | 13 + cprint/color.go | 5 + diff/diff.go | 67 +- diff/diff_helpers.go | 2 +- diff/diff_helpers_test.go | 2 +- dump/dump.go | 50 +- file/builder.go | 148 +- file/builder_test.go | 42 +- file/codegen/main.go | 12 +- file/kong2kic.go | 1158 ++++++++++ file/kong2kic_test.go | 209 ++ file/kong_json_schema.json | 12 +- file/reader.go | 4 +- file/reader_test.go | 8 +- file/readfile.go | 118 +- file/readfile_test.go | 54 +- file/testdata/differentruntimegroup/bar.yaml | 6 + file/testdata/differentruntimegroup/foo.yaml | 6 + .../annotations/json/1-service/input.yaml | 16 + .../json/1-service/output-expected.json | 31 + .../10-mulitple-plugins-same-route/input.yaml | 194 ++ .../output-expected.json | 371 ++++ .../json/11-consumer-group/input.yaml | 9 + .../11-consumer-group/output-expected.json | 28 + .../json/2-service-and-route/input.yaml | 44 + .../2-service-and-route/output-expected.json | 76 + .../json/3-service-and-upstream/input.yaml | 97 + .../output-expected.json | 131 ++ .../json/4-service-route-upstream/input.yaml | 125 ++ .../output-expected.json | 176 ++ .../input.yaml | 139 ++ .../output-expected.json | 224 ++ .../input.yaml | 139 ++ .../output-expected.json | 223 ++ .../input.yaml | 151 ++ .../output-expected.json | 238 +++ .../input.yaml | 142 ++ .../output-expected.json | 227 ++ .../input.yaml | 140 ++ .../output-expected.json | 228 ++ .../annotations/yaml/1-service/input.yaml | 16 + .../yaml/1-service/output-expected.yaml | 22 + .../10-mulitple-plugins-same-route/input.yaml | 194 ++ .../output-expected.yaml | 292 +++ .../yaml/11-consumer-group/input.yaml | 9 + .../11-consumer-group/output-expected.yaml | 22 + .../yaml/2-service-and-route/input.yaml | 44 + .../2-service-and-route/output-expected.yaml | 53 + .../yaml/3-service-and-upstream/input.yaml | 97 + .../output-expected.yaml | 105 + .../yaml/4-service-route-upstream/input.yaml | 125 ++ .../output-expected.yaml | 136 ++ .../input.yaml | 139 ++ .../output-expected.yaml | 174 ++ .../input.yaml | 139 ++ .../output-expected.yaml | 174 ++ .../input.yaml | 151 ++ .../output-expected.yaml | 186 ++ .../input.yaml | 142 ++ .../output-expected.yaml | 177 ++ .../input.yaml | 140 ++ .../output-expected.yaml | 178 ++ .../json/1-service/input.yaml | 16 + .../json/1-service/output-expected.json | 44 + .../10-mulitple-plugins-same-route/input.yaml | 194 ++ .../output-expected.json | 401 ++++ .../json/11-consumer-group/input.yaml | 9 + .../11-consumer-group/output-expected.json | 28 + .../json/2-service-and-route/input.yaml | 44 + .../2-service-and-route/output-expected.json | 117 ++ .../json/3-service-and-upstream/input.yaml | 97 + .../output-expected.json | 133 ++ .../json/4-service-route-upstream/input.yaml | 125 ++ .../output-expected.json | 206 ++ .../input.yaml | 139 ++ .../output-expected.json | 254 +++ .../input.yaml | 139 ++ .../output-expected.json | 253 +++ .../input.yaml | 151 ++ .../output-expected.json | 268 +++ .../input.yaml | 142 ++ .../output-expected.json | 257 +++ .../input.yaml | 140 ++ .../output-expected.json | 258 +++ .../yaml/1-service/input.yaml | 16 + .../yaml/1-service/output-expected.yaml | 32 + .../10-mulitple-plugins-same-route/input.yaml | 194 ++ .../output-expected.yaml | 312 +++ .../yaml/11-consumer-group/input.yaml | 9 + .../11-consumer-group/output-expected.yaml | 22 + .../yaml/2-service-and-route/input.yaml | 44 + .../2-service-and-route/output-expected.yaml | 82 + .../yaml/3-service-and-upstream/input.yaml | 97 + .../output-expected.yaml | 106 + .../yaml/4-service-route-upstream/input.yaml | 125 ++ .../output-expected.yaml | 156 ++ .../input.yaml | 139 ++ .../output-expected.yaml | 194 ++ .../input.yaml | 139 ++ .../output-expected.yaml | 194 ++ .../input.yaml | 151 ++ .../output-expected.yaml | 206 ++ .../input.yaml | 142 ++ .../output-expected.yaml | 197 ++ .../input.yaml | 140 ++ .../output-expected.yaml | 198 ++ file/types.go | 246 ++- file/validate.go | 29 +- file/writer.go | 90 +- file/writer_test.go | 2 +- go.mod | 92 +- go.sum | 899 +++++++- state/builder.go | 23 +- state/consumer.go | 41 +- state/consumer_test.go | 16 +- state/plugin.go | 56 +- state/plugin_test.go | 55 +- state/types.go | 129 ++ tests/integration/diff_test.go | 507 ++++- tests/integration/dump_test.go | 98 +- tests/integration/ping_test.go | 14 +- tests/integration/reset_test.go | 6 +- tests/integration/sync_test.go | 1872 +++++++++++++---- tests/integration/test_utils.go | 32 +- .../expected-no-skip-34.yaml | 60 + .../expected-no-skip-35.yaml | 60 + .../expected-no-skip_konnect.yaml | 62 + .../002-skip-consumers/expected_konnect.yaml | 13 + .../dump/002-skip-consumers/kong34.yaml | 19 + .../kong3x-empty-application.yaml | 48 + .../sync/019-skip-consumers/kong34.yaml | 49 + .../initial.yaml | 55 + .../update.yaml | 55 + .../kong3x.yaml | 12 + .../kong3x.yaml | 79 + .../konnect.yaml | 79 + types/certificate.go | 42 +- types/consumer.go | 38 +- types/core.go | 4 +- types/plugin.go | 22 +- utils/constants.go | 2 +- utils/defaulter_test.go | 8 +- utils/types.go | 11 + utils/utils.go | 19 + 195 files changed, 20215 insertions(+), 948 deletions(-) create mode 100644 .DS_Store create mode 100644 .gitattributes create mode 100644 .github/workflows/validate-kong-release.yaml create mode 100644 .vscode/launch.json delete mode 100644 cmd/convert.go create mode 100644 cmd/file.go create mode 100644 cmd/file_addplugins.go create mode 100644 cmd/file_addtags.go create mode 100644 cmd/file_convert.go create mode 100644 cmd/file_kong2kic.go create mode 100644 cmd/file_listtags.go create mode 100644 cmd/file_merge.go create mode 100644 cmd/file_openapi2kong.go create mode 100644 cmd/file_patch.go create mode 100644 cmd/file_removetags.go create mode 100644 cmd/file_render.go create mode 100644 convert/.gitignore delete mode 100644 convert/testdata/3/output.yaml delete mode 100644 convert/testdata/4/output.yaml create mode 100644 convert/testdata/5/input.yaml create mode 100644 convert/testdata/5/output-expected.yaml create mode 100644 convert/testdata/6/input.yaml create mode 100644 convert/testdata/6/output-expected.yaml create mode 100644 convert/testdata/7/input-1.yaml create mode 100644 convert/testdata/7/input-2.yaml create mode 100644 convert/testdata/7/output-expected.yaml create mode 100644 convert/testdata/8/input.yaml create mode 100644 convert/testdata/8/output-expected.yaml create mode 100644 convert/testdata/9/input.yaml create mode 100644 convert/testdata/9/output-expected.yaml create mode 100644 file/kong2kic.go create mode 100644 file/kong2kic_test.go create mode 100644 file/testdata/differentruntimegroup/bar.yaml create mode 100644 file/testdata/differentruntimegroup/foo.yaml create mode 100644 file/testdata/kong2kic/annotations/json/1-service/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/1-service/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/json/11-consumer-group/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/11-consumer-group/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/json/2-service-and-route/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/2-service-and-route/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/json/3-service-and-upstream/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/3-service-and-upstream/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/json/4-service-route-upstream/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/4-service-route-upstream/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/input.yaml create mode 100644 file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/output-expected.json create mode 100644 file/testdata/kong2kic/annotations/yaml/1-service/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/1-service/output-expected.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/output-expected.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/11-consumer-group/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/11-consumer-group/output-expected.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/2-service-and-route/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/2-service-and-route/output-expected.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/output-expected.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/output-expected.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/output-expected.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/output-expected.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/output-expected.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/input.yaml create mode 100644 file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/1-service/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/1-service/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/json/11-consumer-group/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/11-consumer-group/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/json/2-service-and-route/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/2-service-and-route/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/output-expected.json create mode 100644 file/testdata/kong2kic/custom_resources/yaml/1-service/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/1-service/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/output-expected.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/input.yaml create mode 100644 file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml create mode 100644 tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-34.yaml create mode 100644 tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-35.yaml create mode 100644 tests/integration/testdata/dump/002-skip-consumers/expected-no-skip_konnect.yaml create mode 100644 tests/integration/testdata/dump/002-skip-consumers/expected_konnect.yaml create mode 100644 tests/integration/testdata/dump/002-skip-consumers/kong34.yaml create mode 100644 tests/integration/testdata/sync/017-consumer-groups-rla-application/kong3x-empty-application.yaml create mode 100644 tests/integration/testdata/sync/019-skip-consumers/kong34.yaml create mode 100644 tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/initial.yaml create mode 100644 tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/update.yaml create mode 100644 tests/integration/testdata/sync/024-consumers-with-custom_id-and-username/kong3x.yaml create mode 100644 tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml create mode 100644 tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/konnect.yaml diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9ae56e7039725bb90c7e5bbd67616c86abc74028 GIT binary patch literal 6148 zcmeH~O$x$5422WzLU7Zi%h`AUZ!n0SpcioA4??LR=z5OsOC|_b*CO%)$(v**lzxj& zBOFOi6$Q z{)~V&n{KnlOU2##_Ig&|Mb*{?4)x;*FFyfD>?&Ts-LRi*0nMSMDk?Dk2sj1?68I{C E2N-Y?vj6}9 literal 0 HcmV?d00001 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..db6afde65 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +file/kong_json_schema.json linguist-generated=true diff --git a/.github/workflows/integration-enterprise.yaml b/.github/workflows/integration-enterprise.yaml index 80637d85b..2047a7096 100644 --- a/.github/workflows/integration-enterprise.yaml +++ b/.github/workflows/integration-enterprise.yaml @@ -23,6 +23,7 @@ jobs: - 'kong/kong-gateway:3.0' - 'kong/kong-gateway:3.1' - 'kong/kong-gateway:3.2' + - 'kong/kong-gateway:3.3' - 'kong/kong-gateway-dev:latest' env: KONG_ANONYMOUS_REPORTS: "off" diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 53d7d9d97..ed8d86174 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -25,6 +25,7 @@ jobs: - 'kong:3.0' - 'kong:3.1' - 'kong:3.2' + - 'kong:3.3' - 'kong/kong:master-alpine' env: KONG_ANONYMOUS_REPORTS: "off" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 58e6ece1b..36cc2b04d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -57,7 +57,7 @@ jobs: password: ${{ secrets.DOCKER_TOKEN }} - name: Docker meta id: meta - uses: docker/metadata-action@v4.4.0 + uses: docker/metadata-action@v4.6.0 with: images: kong/deck tags: ${{ env.TAGS_STANDARD }}${{ env.TAGS_SUPPLEMENTAL }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 696eb4097..c3a2929c8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,7 +17,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.4.0 + uses: golangci/golangci-lint-action@v3.6.0 - name: Verify Codegen run: make verify-codegen - name: Run tests with Coverage @@ -26,6 +26,7 @@ jobs: uses: codecov/codecov-action@v3 with: name: codecov-deck + token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true - name: Build run: make build diff --git a/.github/workflows/validate-kong-release.yaml b/.github/workflows/validate-kong-release.yaml new file mode 100644 index 000000000..af6c10715 --- /dev/null +++ b/.github/workflows/validate-kong-release.yaml @@ -0,0 +1,39 @@ +name: Validate Kong Gateway Release +concurrency: + group: ${{ github.workflow }} +on: + workflow_dispatch: + inputs: + kong_image: + description: 'Kong Gateway Docker Image' + required: true + default: 'kong/kong-gateway-dev:latest' + branch: + description: 'decK Branch' + required: true + default: 'main' +jobs: + integration: + name: "${{ inputs.kong_image }} against ${{ inputs.branch }}" + env: + KONG_ANONYMOUS_REPORTS: "off" + KONG_IMAGE: ${{ inputs.kong_image }} + KONG_LICENSE_DATA: ${{ secrets.KONG_LICENSE_DATA }} + runs-on: ubuntu-latest + steps: + - name: Execution Information + run: | + echo "Kong Gateway Image = ${{ inputs.kong_image }}" + echo "decK Branch = ${{ inputs.branch }}" + - name: Setup go + uses: actions/setup-go@v4 + with: + go-version: '^1.20' + - name: Checkout repository + uses: actions/checkout@v3 + with: + ref: ${{ inputs.branch }} + - name: Setup Kong + run: make setup-kong-ee + - name: Run integration tests + run: make test-integration diff --git a/.gitignore b/.gitignore index 9b579130f..80b2fc161 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,9 @@ _testmain.go deck dist/ docs/cli-docs/ +.idea/deck.iml +.idea/misc.xml +.gitignore +.idea/vcs.xml +.gitignore +.idea/modules.xml diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..5c7247b40 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index dbfa476ed..43d0cd05f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Table of Contents +- [v1.26.1](#v1261) +- [v1.26.0](#v1260) +- [v1.25.0](#v1250) +- [v1.24.0](#v1240) +- [v1.23.0](#v1230) - [v1.22.1](#v1221) - [v1.22.0](#v1220) - [v1.21.0](#v1210) @@ -59,6 +64,105 @@ - [v0.2.0](#v020) - [v0.1.0](#v010) +## [v1.26.1] + +> Release date: 2023/09/07 + +### Fixes + +- Raise an error if state files have different Runtime Groups + [#1014](https://github.com/Kong/deck/pull/1014) +- Correct consumers validation when `custom_id` is used + [#1012](https://github.com/Kong/deck/pull/1012) +- Remove hardcoded default value for Routes' `strip_path` field. Defaults are pulled via + API anyway. + [#999](https://github.com/Kong/deck/pull/999) + +## [v1.26.0] + +> Release date: 2023/08/09 + +### Added + +- Added support for scoping plugins to Consumer Groups for both Kong Gateway and Konnect. + [#963](https://github.com/Kong/deck/pull/963) + [#959](https://github.com/Kong/deck/pull/959) + +### Fixes + +- Remove fallback mechanism formely used to authenticate with either "old" or "new" Konnect. + [#995](https://github.com/Kong/deck/pull/995) + +## [v1.25.0] + +> Release date: 2023/07/28 + +### Added + +- Added a new command `file render` to render a final decK file. This will result in a file representing + the state as it would be synced online. + [#963](https://github.com/Kong/deck/pull/963) +- Added a new flag `--format` to `file convert` to enable JSON output. + [#963](https://github.com/Kong/deck/pull/963) + +### Fixes + +- Use same interface to pull Consumer Groups with Kong Gateway and Konnect. + This will help solving the issue of using tags with Consumer Groups when running against Konnect. + [#984](https://github.com/Kong/deck/pull/984) +- Fix Consumers handling when a consumer's `custom_id` is equal to the `username` of another consumer. + [#986](https://github.com/Kong/deck/pull/986) +- Avoid misleading diffs when configuration file has empty tags. + [#985](https://github.com/Kong/deck/pull/985) + +## [v1.24.0] + +> Release date: 2023/07/24 + +### Added + +- Add a new flag (`--json-output`) to enable JSON output when using `sync` and `diff` commands + [#798](https://github.com/Kong/deck/pull/798) +- Improved error logs coming from files validation against Kong's schemas. + [#976](https://github.com/Kong/deck/pull/976) +- Added a new command `file openapi2kong` that will generate a deck file from an OpenAPI + 3.0 spec. This is the replacement for the similar `inso` functionality. + The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops). + [#939](https://github.com/Kong/deck/pull/939) +- Added a new command `file merge` that will merge multiple deck files. The files will not be + validated, which allows for working with incomplete or even invalid files in a pipeline. + The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops). + [#939](https://github.com/Kong/deck/pull/939) +- Added a new command `file patch` for applying patches on top of a decK file. The patches can be + provided on the commandline, or via patch files. The deck file will not be + validated, which allows for working with incomplete or even invalid files in a pipeline. + The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops). + [#939](https://github.com/Kong/deck/pull/939) +- Added a new commands `file add-tags/list-tags/remove-tags` to manage tags in a decK file. The deck file will not be + validated, which allows for working with incomplete or even invalid files in a pipeline. + The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops). + [#939](https://github.com/Kong/deck/pull/939) +- Added a new command `file add-plugins` for adding plugins to a decK file. The plugins can be + provided on the commandline, or via config files. The deck file will not be + validated, which allows for working with incomplete or even invalid files in a pipeline. + The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops). + [#939](https://github.com/Kong/deck/pull/939) + +### Fixes + +- Fix Certificates & SNIs handling when running against Konnect. + [#978](https://github.com/Kong/deck/pull/978) + + +## [v1.23.0] + +> Release date: 2023/07/03 + +### Add + +- Honor HTTPS_PROXY and HTTP_PROXY proxy environment variables + [#952](https://github.com/Kong/deck/pull/952) + ## [v1.22.1] > Release date: 2023/06/22 @@ -1254,6 +1358,11 @@ No breaking changes have been introduced in this release. Debut release of decK +[v1.26.1]: https://github.com/kong/deck/compare/v1.26.0...v1.26.1 +[v1.26.0]: https://github.com/kong/deck/compare/v1.25.0...v1.26.0 +[v1.25.0]: https://github.com/kong/deck/compare/v1.24.0...v1.25.0 +[v1.24.0]: https://github.com/kong/deck/compare/v1.23.0...v1.24.0 +[v1.23.0]: https://github.com/kong/deck/compare/v1.22.1...v1.23.0 [v1.22.1]: https://github.com/kong/deck/compare/v1.22.0...v1.22.1 [v1.22.0]: https://github.com/kong/deck/compare/v1.21.0...v1.22.0 [v1.21.0]: https://github.com/kong/deck/compare/v1.20.0...v1.21.0 diff --git a/Dockerfile b/Dockerfile index fc8cbc547..aaae20c36 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20.5 AS build +FROM golang:1.21.0 AS build WORKDIR /deck COPY go.mod ./ COPY go.sum ./ @@ -9,7 +9,7 @@ ARG TAG RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o deck \ -ldflags "-s -w -X github.com/kong/deck/cmd.VERSION=$TAG -X github.com/kong/deck/cmd.COMMIT=$COMMIT" -FROM alpine:3.18.2 +FROM alpine:3.18.3 RUN adduser --disabled-password --gecos "" deckuser RUN apk --no-cache add ca-certificates jq USER deckuser diff --git a/Makefile b/Makefile index dc40d45f2..7346c2b93 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ test-all: lint test .PHONY: test test: - go test -race ./... + go test -race -count=1 ./... .PHONY: lint lint: @@ -47,6 +47,6 @@ setup-kong-ee: .PHONY: test-integration test-integration: - go test -v -tags=integration \ + go test -v -count=1 -tags=integration \ -race \ ./tests/integration/... \ No newline at end of file diff --git a/cmd/common.go b/cmd/common.go index ede3d0421..61cc62c09 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -2,6 +2,8 @@ package cmd import ( "context" + "encoding/json" + "errors" "fmt" "net/http" "os" @@ -39,6 +41,8 @@ const ( modeKongEnterprise ) +var jsonOutput diff.JSONOutputObject + func getMode(targetContent *file.Content) mode { if inKonnectMode(targetContent) { return modeKonnect @@ -71,20 +75,36 @@ func workspaceExists(ctx context.Context, config utils.KongClientConfig, workspa return exists, nil } -func getWorkspaceName(workspaceFlag string, targetContent *file.Content) string { +func getWorkspaceName(workspaceFlag string, targetContent *file.Content, + enableJSONOutput bool, +) string { if workspaceFlag != targetContent.Workspace && workspaceFlag != "" { - cprint.DeletePrintf("Warning: Workspace '%v' specified via --workspace flag is "+ - "different from workspace '%v' found in state file(s).\n", workspaceFlag, targetContent.Workspace) + warning := fmt.Sprintf("Workspace '%v' specified via --workspace flag is "+ + "different from workspace '%v' found in state file(s).", workspaceFlag, targetContent.Workspace) + if enableJSONOutput { + jsonOutput.Warnings = append(jsonOutput.Warnings, warning) + } else { + cprint.DeletePrintf("Warning: " + warning + "\n") + } return workspaceFlag } return targetContent.Workspace } func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, - delay int, workspace string, + delay int, workspace string, enableJSONOutput bool, ) error { // read target file - targetContent, err := file.GetContentFromFiles(filenames) + if enableJSONOutput { + jsonOutput.Errors = []string{} + jsonOutput.Warnings = []string{} + jsonOutput.Changes = diff.EntityChanges{ + Creating: []diff.EntityState{}, + Updating: []diff.EntityState{}, + Deleting: []diff.EntityState{}, + } + } + targetContent, err := file.GetContentFromFiles(filenames, false) if err != nil { return err } @@ -137,7 +157,7 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, // prepare to read the current state from Kong var wsConfig utils.KongClientConfig - workspaceName := getWorkspaceName(workspace, targetContent) + workspaceName := getWorkspaceName(workspace, targetContent, enableJSONOutput) wsConfig = rootConfig.ForWorkspace(workspaceName) // load Kong version after workspace @@ -192,6 +212,10 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, return err } + if utils.Kong340Version.LTE(parsedKongVersion) { + dumpConfig.IsConsumerGroupScopedPluginSupported = true + } + // read the current state var currentState *state.KongState if workspaceExists { @@ -206,7 +230,15 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, return err } - cprint.CreatePrintln("creating workspace", wsConfig.Workspace) + if enableJSONOutput { + workspace := diff.EntityState{ + Name: wsConfig.Workspace, + Kind: "workspace", + } + jsonOutput.Changes.Creating = append(jsonOutput.Changes.Creating, workspace) + } else { + cprint.CreatePrintln("Creating workspace", wsConfig.Workspace) + } if !dry { _, err = rootClient.Workspaces.Create(ctx, &kong.Workspace{Name: &wsConfig.Workspace}) if err != nil { @@ -232,14 +264,34 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, } totalOps, err := performDiff( - ctx, currentState, targetState, dry, parallelism, delay, kongClient, mode == modeKonnect) + ctx, currentState, targetState, dry, parallelism, delay, kongClient, mode == modeKonnect, enableJSONOutput) if err != nil { - return err + if enableJSONOutput { + var errs utils.ErrArray + if errors.As(err, &errs) { + jsonOutput.Errors = append(jsonOutput.Errors, errs.ErrorList()...) + } else { + jsonOutput.Errors = append(jsonOutput.Errors, err.Error()) + } + } else { + return err + } } - if diffCmdNonZeroExitCode && totalOps > 0 { os.Exit(exitCodeDiffDetection) } + if enableJSONOutput { + jsonOutputBytes, jsonErr := json.MarshalIndent(jsonOutput, "", "\t") + if jsonErr != nil { + return err + } + jsonOutputString := string(jsonOutputBytes) + if !noMaskValues { + jsonOutputString = diff.MaskEnvVarValue(jsonOutputString) + } + + cprint.BluePrintLn(jsonOutputString + "\n") + } return nil } @@ -281,6 +333,7 @@ func fetchCurrentState(ctx context.Context, client *kong.Client, dumpConfig dump func performDiff(ctx context.Context, currentState, targetState *state.KongState, dry bool, parallelism int, delay int, client *kong.Client, isKonnect bool, + enableJSONOutput bool, ) (int, error) { s, err := diff.NewSyncer(diff.SyncerOpts{ CurrentState: currentState, @@ -294,13 +347,29 @@ func performDiff(ctx context.Context, currentState, targetState *state.KongState return 0, err } - stats, errs := s.Solve(ctx, parallelism, dry) + stats, errs, changes := s.Solve(ctx, parallelism, dry, enableJSONOutput) // print stats before error to report completed operations - printStats(stats) + if !enableJSONOutput { + printStats(stats) + } if errs != nil { return 0, utils.ErrArray{Errors: errs} } totalOps := stats.CreateOps.Count() + stats.UpdateOps.Count() + stats.DeleteOps.Count() + + if enableJSONOutput { + jsonOutput.Changes = diff.EntityChanges{ + Creating: append(jsonOutput.Changes.Creating, changes.Creating...), + Updating: append(jsonOutput.Changes.Updating, changes.Updating...), + Deleting: append(jsonOutput.Changes.Deleting, changes.Deleting...), + } + jsonOutput.Summary = diff.Summary{ + Creating: stats.CreateOps.Count(), + Updating: stats.UpdateOps.Count(), + Deleting: stats.DeleteOps.Count(), + Total: totalOps, + } + } return int(totalOps), nil } diff --git a/cmd/common_konnect.go b/cmd/common_konnect.go index f6a2b5f1b..850af8391 100644 --- a/cmd/common_konnect.go +++ b/cmd/common_konnect.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/http" - "net/url" "os" "strings" @@ -18,27 +17,12 @@ import ( "golang.org/x/sync/errgroup" ) -const ( - defaultLegacyKonnectURL = "https://konnect.konghq.com" - - defaultRuntimeGroupName = "default" - konnectWithRuntimeGroupsDomain = "api.konghq" -) - -var addresses = []string{ - defaultKonnectURL, - defaultLegacyKonnectURL, -} +const defaultRuntimeGroupName = "default" func authenticate( - ctx context.Context, client *konnect.Client, host string, konnectConfig utils.KonnectConfig, + ctx context.Context, client *konnect.Client, konnectConfig utils.KonnectConfig, ) (konnect.AuthResponse, error) { - if strings.Contains(host, konnectWithRuntimeGroupsDomain) { - return client.Auth.LoginV2(ctx, konnectConfig.Email, - konnectConfig.Password, konnectConfig.Token) - } - return client.Auth.Login(ctx, konnectConfig.Email, - konnectConfig.Password) + return client.Auth.LoginV2(ctx, konnectConfig.Email, konnectConfig.Password, konnectConfig.Token) } // GetKongClientForKonnectMode abstracts the different cloud environments users @@ -50,9 +34,6 @@ func GetKongClientForKonnectMode( ctx context.Context, konnectConfig *utils.KonnectConfig, ) (*kong.Client, error) { httpClient := utils.HTTPClient() - if konnectConfig.Address != defaultKonnectURL { - addresses = []string{konnectConfig.Address} - } if konnectConfig.Token != "" { konnectConfig.Headers = append( @@ -63,57 +44,25 @@ func GetKongClientForKonnectMode( // authenticate with konnect var err error var konnectClient *konnect.Client - var parsedAddress *url.URL var konnectAddress string - for _, address := range addresses { - // get Konnect client - konnectConfig.Address = address - konnectClient, err = utils.GetKonnectClient(httpClient, *konnectConfig) - if err != nil { - return nil, err - } - parsedAddress, err = url.Parse(address) - if err != nil { - return nil, fmt.Errorf("parsing %s address: %w", address, err) - } - _, err = authenticate(ctx, konnectClient, parsedAddress.Host, *konnectConfig) - if err == nil { - break - } - // Personal Access Token authentication is not supported with the - // legacy Konnect, so we don't need to fallback in case of 401s. - if konnect.IsUnauthorizedErr(err) && konnectConfig.Token != "" { - return nil, fmt.Errorf("authenticating with Konnect: %w", err) - } - if konnect.IsUnauthorizedErr(err) { - continue - } + // get Konnect client + konnectClient, err = utils.GetKonnectClient(httpClient, *konnectConfig) + if err != nil { + return nil, err } + _, err = authenticate(ctx, konnectClient, *konnectConfig) if err != nil { return nil, fmt.Errorf("authenticating with Konnect: %w", err) } - if strings.Contains(parsedAddress.Host, konnectWithRuntimeGroupsDomain) { - // get kong runtime group ID - kongRGID, err := fetchKongRuntimeGroupID(ctx, konnectClient) - if err != nil { - return nil, err - } - - // set the kong runtime group ID in the client - konnectClient.SetRuntimeGroupID(kongRGID) - konnectAddress = konnectConfig.Address + "/konnect-api/api/runtime_groups/" + kongRGID - } else { - // get kong control plane ID - kongCPID, err := fetchKongControlPlaneID(ctx, konnectClient) - if err != nil { - return nil, err - } - - // set the kong control plane ID in the client - konnectClient.SetControlPlaneID(kongCPID) - konnectAddress = konnectConfig.Address + "/api/control_planes/" + kongCPID + kongRGID, err := fetchKongRuntimeGroupID(ctx, konnectClient) + if err != nil { + return nil, err } + // set the kong runtime group ID in the client + konnectClient.SetRuntimeGroupID(kongRGID) + konnectAddress = konnectConfig.Address + "/konnect-api/api/runtime_groups/" + kongRGID + // initialize kong client return utils.GetKongClient(utils.KongClientConfig{ Address: konnectAddress, @@ -140,7 +89,7 @@ func resetKonnectV2(ctx context.Context) error { if err != nil { return err } - _, err = performDiff(ctx, currentState, targetState, false, 10, 0, client, true) + _, err = performDiff(ctx, currentState, targetState, false, 10, 0, client, true, resetJSONOutput) if err != nil { return err } @@ -183,7 +132,7 @@ func syncKonnect(ctx context.Context, httpClient := utils.HTTPClient() // read target file - targetContent, err := file.GetContentFromFiles(filenames) + targetContent, err := file.GetContentFromFiles(filenames, false) if err != nil { return err } @@ -258,7 +207,7 @@ func syncKonnect(ctx context.Context, return err } - stats, errs := s.Solve(ctx, parallelism, dry) + stats, errs, _ := s.Solve(ctx, parallelism, dry, false) // print stats before error to report completed operations printStats(stats) if errs != nil { diff --git a/cmd/convert.go b/cmd/convert.go deleted file mode 100644 index 0476ab2e2..000000000 --- a/cmd/convert.go +++ /dev/null @@ -1,96 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/kong/deck/convert" - "github.com/kong/deck/cprint" - "github.com/kong/deck/utils" - "github.com/spf13/cobra" -) - -var ( - convertCmdSourceFormat string - convertCmdDestinationFormat string - convertCmdInputFile string - convertCmdOutputFile string - convertCmdAssumeYes bool -) - -// newConvertCmd represents the convert command -func newConvertCmd() *cobra.Command { - convertCmd := &cobra.Command{ - Use: "convert", - Short: "Convert files from one format into another format", - Long: `The convert command changes configuration files from one format -into another compatible format. For example, a configuration for 'kong-gateway-2.x' -can be converted into a 'kong-gateway-3.x' configuration file.`, - Args: validateNoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - sourceFormat, err := convert.ParseFormat(convertCmdSourceFormat) - if err != nil { - return err - } - destinationFormat, err := convert.ParseFormat(convertCmdDestinationFormat) - if err != nil { - return err - } - - if convertCmdInputFile != "" { - if yes, err := utils.ConfirmFileOverwrite( - convertCmdOutputFile, "", convertCmdAssumeYes, - ); err != nil { - return err - } else if !yes { - return nil - } - - err = convert.Convert(convertCmdInputFile, convertCmdOutputFile, sourceFormat, destinationFormat) - if err != nil { - return fmt.Errorf("converting file: %w", err) - } - } else if is2xTo3xConversion() { - path, err := os.Getwd() - if err != nil { - return fmt.Errorf("getting current working directory: %w", err) - } - files, err := utils.ConfigFilesInDir(path) - if err != nil { - return fmt.Errorf("getting files from directory: %w", err) - } - for _, filename := range files { - err = convert.Convert(filename, filename, sourceFormat, destinationFormat) - if err != nil { - return fmt.Errorf("converting '%s' file: %w", filename, err) - } - } - } - if convertCmdDestinationFormat == "konnect" { - cprint.UpdatePrintf("Warning: konnect format type was deprecated in v1.12 and it will be removed\n" + - "in a future version. Please use your Kong configuration files with deck .\n" + - "Please see https://docs.konghq.com/konnect/getting-started/import/.\n") - } - return nil - }, - } - - sourceFormats := []convert.Format{convert.FormatKongGateway, convert.FormatKongGateway2x} - destinationFormats := []convert.Format{convert.FormatKonnect, convert.FormatKongGateway3x} - convertCmd.Flags().StringVar(&convertCmdSourceFormat, "from", "", - fmt.Sprintf("format of the source file, allowed formats: %v", sourceFormats)) - convertCmd.Flags().StringVar(&convertCmdDestinationFormat, "to", "", - fmt.Sprintf("desired format of the output, allowed formats: %v", destinationFormats)) - convertCmd.Flags().StringVar(&convertCmdInputFile, "input-file", "", - "configuration file to be converted. Use `-` to read from stdin.") - convertCmd.Flags().StringVar(&convertCmdOutputFile, "output-file", "kong.yaml", - "file to write configuration to after conversion. Use `-` to write to stdout.") - convertCmd.Flags().BoolVar(&convertCmdAssumeYes, "yes", - false, "assume `yes` to prompts and run non-interactively.") - return convertCmd -} - -func is2xTo3xConversion() bool { - return convertCmdSourceFormat == string(convert.FormatKongGateway2x) && - convertCmdDestinationFormat == string(convert.FormatKongGateway3x) -} diff --git a/cmd/diff.go b/cmd/diff.go index 8bb66ff92..b98df6692 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -11,6 +11,7 @@ var ( diffCmdParallelism int diffCmdNonZeroExitCode bool diffWorkspace string + diffJSONOutput bool ) // newDiffCmd represents the diff command @@ -27,7 +28,7 @@ that will be created, updated, or deleted. Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { return syncMain(cmd.Context(), diffCmdKongStateFile, true, - diffCmdParallelism, 0, diffWorkspace) + diffCmdParallelism, 0, diffWorkspace, diffJSONOutput) }, PreRunE: func(cmd *cobra.Command, args []string) error { if len(diffCmdKongStateFile) == 0 { @@ -65,6 +66,8 @@ that will be created, updated, or deleted. "and exit code 1 if an error occurs.") diffCmd.Flags().BoolVar(&dumpConfig.SkipCACerts, "skip-ca-certificates", false, "do not diff CA certificates.") + diffCmd.Flags().BoolVar(&diffJSONOutput, "json-output", + false, "generate command execution report in a JSON format") addSilenceEventsFlag(diffCmd.Flags()) return diffCmd } diff --git a/cmd/file.go b/cmd/file.go new file mode 100644 index 000000000..71b767f4a --- /dev/null +++ b/cmd/file.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func newAddFileCmd() *cobra.Command { + addFileCmd := &cobra.Command{ + Use: "file [sub-command]...", + Short: "Subcommand to host the decK file manipulation operations", + Long: `Subcommand to host the decK file manipulation operations.`, + } + + return addFileCmd +} diff --git a/cmd/file_addplugins.go b/cmd/file_addplugins.go new file mode 100644 index 000000000..b24956b25 --- /dev/null +++ b/cmd/file_addplugins.go @@ -0,0 +1,161 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/jsonbasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/plugins" + "github.com/spf13/cobra" +) + +var ( + cmdAddPluginsOverwrite bool + cmdAddPluginsInputFilename string + cmdAddPluginOutputFilename string + cmdAddPluginOutputFormat string + cmdAddPluginsSelectors []string + cmdAddPluginsStrConfigs []string +) + +// Executes the CLI command "add-plugins" +func executeAddPlugins(cmd *cobra.Command, cfgFiles []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + cmdAddPluginOutputFormat = strings.ToUpper(cmdAddPluginOutputFormat) + + var pluginConfigs []map[string]interface{} + { + for _, strConfig := range cmdAddPluginsStrConfigs { + pluginConfig, err := filebasics.Deserialize([]byte(strConfig)) + if err != nil { + return fmt.Errorf("failed to deserialize plugin config '%s'; %w", strConfig, err) + } + pluginConfigs = append(pluginConfigs, pluginConfig) + } + } + + var pluginFiles []plugins.DeckPluginFile + { + for _, filename := range cfgFiles { + var file plugins.DeckPluginFile + if err := file.ParseFile(filename); err != nil { + return fmt.Errorf("failed to parse plugin file '%s'; %w", filename, err) + } + pluginFiles = append(pluginFiles, file) + } + } + + // do the work: read/add-plugins/write + jsondata, err := filebasics.DeserializeFile(cmdAddPluginsInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdAddPluginsInputFilename, err) + } + yamlNode := jsonbasics.ConvertToYamlNode(jsondata) + + // apply CLI flags + plugger := plugins.Plugger{} + plugger.SetYamlData(yamlNode) + err = plugger.SetSelectors(cmdAddPluginsSelectors) + if err != nil { + return fmt.Errorf("failed to set selectors; %w", err) + } + err = plugger.AddPlugins(pluginConfigs, cmdAddPluginsOverwrite) + if err != nil { + return fmt.Errorf("failed to add plugins; %w", err) + } + yamlNode = plugger.GetYamlData() + + // apply plugin-files + for i, pluginFile := range pluginFiles { + err = pluginFile.Apply(yamlNode) + if err != nil { + return fmt.Errorf("failed to apply plugin file '%s'; %w", cfgFiles[i], err) + } + } + jsondata = plugger.GetData() + + trackInfo := deckformat.HistoryNewEntry("add-plugins") + trackInfo["input"] = cmdAddPluginsInputFilename + trackInfo["output"] = cmdAddPluginOutputFilename + trackInfo["overwrite"] = cmdAddPluginsOverwrite + if len(pluginConfigs) > 0 { + trackInfo["configs"] = pluginConfigs + } + if len(cfgFiles) > 0 { + trackInfo["pluginfiles"] = cfgFiles + } + trackInfo["selectors"] = cmdAddPluginsSelectors + deckformat.HistoryAppend(jsondata, trackInfo) + + return filebasics.WriteSerializedFile( + cmdAddPluginOutputFilename, + jsondata, + filebasics.OutputFormat(cmdAddPluginOutputFormat)) +} + +// +// +// Define the CLI data for the add-plugins command +// +// + +func newAddPluginsCmd() *cobra.Command { + addPluginsCmd := &cobra.Command{ + Use: "add-plugins [flags] [...plugin-files]", + Short: "Add plugins to objects in a decK file", + Long: `Add plugins to objects in a decK file. + +The plugins are added to all objects that match the selector expressions. If no +selectors are given, the plugins are added to the top-level 'plugins' array. + +The plugin files have the following format (JSON or YAML) and are applied in the +order they are given: + + { "_format_version": "1.0", + "add-plugins": [ + { "selectors": [ + "$..services[*]" + ], + "overwrite": false, + "plugins": [ + { "name": "my-plugin", + "config": { + "my-property": "value" + } + } + ] + } + ] + } +`, + RunE: executeAddPlugins, + Args: cobra.MinimumNArgs(0), + } + + addPluginsCmd.Flags().StringVarP(&cmdAddPluginsInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + addPluginsCmd.Flags().StringArrayVar(&cmdAddPluginsSelectors, "selector", []string{}, + "JSON path expression to select plugin-owning objects to add plugins to.\n"+ + "Defaults to the top-level (selector '$'). Repeat for multiple selectors.") + addPluginsCmd.Flags().StringArrayVar(&cmdAddPluginsStrConfigs, "config", []string{}, + "JSON snippet containing the plugin configuration to add. Repeat to add\n"+ + "multiple plugins.") + addPluginsCmd.Flags().BoolVar(&cmdAddPluginsOverwrite, "overwrite", false, + "Specify this flag to overwrite plugins by the same name if they already\n"+ + "exist in an array. The default behavior is to skip existing plugins.") + addPluginsCmd.Flags().StringVarP(&cmdAddPluginOutputFilename, "output-file", "o", "-", + "Output file to write to. Use - to write to stdout.") + addPluginsCmd.Flags().StringVarP(&cmdAddPluginOutputFormat, "format", "", string(filebasics.OutputFormatYaml), + "Output format: "+string(filebasics.OutputFormatJSON)+" or "+string(filebasics.OutputFormatYaml)) + + return addPluginsCmd +} diff --git a/cmd/file_addtags.go b/cmd/file_addtags.go new file mode 100644 index 000000000..60cbdba73 --- /dev/null +++ b/cmd/file_addtags.go @@ -0,0 +1,89 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/tags" + "github.com/spf13/cobra" +) + +var ( + cmdAddTagsInputFilename string + cmdAddTagsOutputFilename string + cmdAddTagsOutputFormat string + cmdAddTagsSelectors []string +) + +// Executes the CLI command "add-tags" +func executeAddTags(cmd *cobra.Command, tagsToAdd []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + cmdAddTagsOutputFormat = strings.ToUpper(cmdAddTagsOutputFormat) + + // do the work: read/add-tags/write + data, err := filebasics.DeserializeFile(cmdAddTagsInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdAddTagsInputFilename, err) + } + + tagger := tags.Tagger{} + tagger.SetData(data) + err = tagger.SetSelectors(cmdAddTagsSelectors) + if err != nil { + return fmt.Errorf("failed to set selectors; %w", err) + } + err = tagger.AddTags(tagsToAdd) + if err != nil { + return fmt.Errorf("failed to add tags; %w", err) + } + data = tagger.GetData() + + trackInfo := deckformat.HistoryNewEntry("add-tags") + trackInfo["input"] = cmdAddTagsInputFilename + trackInfo["output"] = cmdAddTagsOutputFilename + trackInfo["tags"] = tagsToAdd + trackInfo["selectors"] = cmdAddTagsSelectors + deckformat.HistoryAppend(data, trackInfo) + + return filebasics.WriteSerializedFile(cmdAddTagsOutputFilename, data, filebasics.OutputFormat(cmdAddTagsOutputFormat)) +} + +// +// +// Define the CLI data for the add-tags command +// +// + +func newAddTagsCmd() *cobra.Command { + addTagsCmd := &cobra.Command{ + Use: "add-tags [flags] tag [...tag]", + Short: "Add tags to objects in a decK file", + Long: `Add tags to objects in a decK file. + +The tags are added to all objects that match the selector expressions. If no +selectors are given, all Kong entities are tagged.`, + RunE: executeAddTags, + Args: cobra.MinimumNArgs(1), + } + + addTagsCmd.Flags().StringVarP(&cmdAddTagsInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + addTagsCmd.Flags().StringArrayVar(&cmdAddTagsSelectors, "selector", []string{}, + "JSON path expression to select objects to add tags to.\n"+ + "Defaults to all Kong entities. Repeat for multiple selectors.") + addTagsCmd.Flags().StringVarP(&cmdAddTagsOutputFilename, "output-file", "o", "-", + "Output file to write to. Use - to write to stdout.") + addTagsCmd.Flags().StringVarP(&cmdAddTagsOutputFormat, "format", "", string(filebasics.OutputFormatYaml), + "Output format: "+string(filebasics.OutputFormatJSON)+" or "+string(filebasics.OutputFormatYaml)) + + return addTagsCmd +} diff --git a/cmd/file_convert.go b/cmd/file_convert.go new file mode 100644 index 000000000..60ec957db --- /dev/null +++ b/cmd/file_convert.go @@ -0,0 +1,119 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/kong/deck/convert" + "github.com/kong/deck/cprint" + "github.com/kong/deck/file" + "github.com/kong/deck/utils" + "github.com/spf13/cobra" +) + +var ( + convertCmdSourceFormat string + convertCmdDestinationFormat string // konnect/kong-gateway-3.x/etc + convertCmdInputFile string + convertCmdOutputFile string + convertCmdAssumeYes bool + convertCmdStateFormat string // yaml/json output +) + +func executeConvert(_ *cobra.Command, _ []string) error { + sourceFormat, err := convert.ParseFormat(convertCmdSourceFormat) + if err != nil { + return err + } + destinationFormat, err := convert.ParseFormat(convertCmdDestinationFormat) + if err != nil { + return err + } + + if convertCmdInputFile != "" { + if yes, err := utils.ConfirmFileOverwrite( + convertCmdOutputFile, "", convertCmdAssumeYes, + ); err != nil { + return err + } else if !yes { + return nil + } + + err = convert.Convert( + []string{convertCmdInputFile}, + convertCmdOutputFile, + file.Format(strings.ToUpper(convertCmdStateFormat)), + sourceFormat, + destinationFormat, + false) + if err != nil { + return fmt.Errorf("converting file: %w", err) + } + } else if is2xTo3xConversion() { + path, err := os.Getwd() + if err != nil { + return fmt.Errorf("getting current working directory: %w", err) + } + files, err := utils.ConfigFilesInDir(path) + if err != nil { + return fmt.Errorf("getting files from directory: %w", err) + } + for _, filename := range files { + err = convert.Convert( + []string{filename}, + filename, + file.Format(strings.ToUpper(convertCmdStateFormat)), + sourceFormat, + destinationFormat, + false) + if err != nil { + return fmt.Errorf("converting '%s' file: %w", filename, err) + } + } + } + if convertCmdDestinationFormat == "konnect" { + cprint.UpdatePrintf("Warning: konnect format type was deprecated in v1.12 and it will be removed\n" + + "in a future version. Please use your Kong configuration files with deck .\n" + + "Please see https://docs.konghq.com/konnect/getting-started/import/.\n") + } + return nil +} + +// newConvertCmd represents the convert command +func newConvertCmd() *cobra.Command { + short := "Convert files from one format into another format" + execute := executeConvert + + convertCmd := &cobra.Command{ + Use: "convert", + Short: short, + Long: `The convert command changes configuration files from one format +into another compatible format. For example, a configuration for 'kong-gateway-2.x' +can be converted into a 'kong-gateway-3.x' configuration file.`, + Args: validateNoArgs, + RunE: execute, + } + + sourceFormats := []convert.Format{convert.FormatKongGateway, convert.FormatKongGateway2x} + destinationFormats := []convert.Format{convert.FormatKonnect, convert.FormatKongGateway3x} + convertCmd.Flags().StringVar(&convertCmdSourceFormat, "from", "", + fmt.Sprintf("format of the source file, allowed formats: %v", sourceFormats)) + convertCmd.Flags().StringVar(&convertCmdDestinationFormat, "to", "", + fmt.Sprintf("desired format of the output, allowed formats: %v", destinationFormats)) + convertCmd.Flags().StringVar(&convertCmdInputFile, "input-file", "", + "configuration file to be converted. Use `-` to read from stdin.") + convertCmd.Flags().StringVar(&convertCmdOutputFile, "output-file", "kong.yaml", + "file to write configuration to after conversion. Use `-` to write to stdout.") + convertCmd.Flags().BoolVar(&convertCmdAssumeYes, "yes", + false, "assume `yes` to prompts and run non-interactively.") + convertCmd.Flags().StringVar(&convertCmdStateFormat, "format", + "yaml", "output file format: json or yaml.") + + return convertCmd +} + +func is2xTo3xConversion() bool { + return convertCmdSourceFormat == string(convert.FormatKongGateway2x) && + convertCmdDestinationFormat == string(convert.FormatKongGateway3x) +} diff --git a/cmd/file_kong2kic.go b/cmd/file_kong2kic.go new file mode 100644 index 000000000..49d32bfb6 --- /dev/null +++ b/cmd/file_kong2kic.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/deck/file" + "github.com/kong/go-apiops/logbasics" + "github.com/spf13/cobra" +) + +var ( + cmdKong2KicInputFilename string + cmdKong2KicOutputFilename string + //cmdKong2KicApi string + cmdKong2KicOutputFormat string + cmdKong2KicManifestStyle string +) + +// Executes the CLI command "kong2kic" +func executeKong2Kic(cmd *cobra.Command, _ []string) error { + + var ( + outputContent *file.Content + err error + outputFileFormat file.Format + ) + + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + inputContent, err := file.GetContentFromFiles([]string{cmdKong2KicInputFilename}, false) + if err != nil { + return fmt.Errorf("failed reding input file '%s'; %w", cmdKong2KicInputFilename, err) + } + + outputContent = inputContent.DeepCopy() + if strings.ToUpper(cmdKong2KicOutputFormat) == "JSON" && strings.ToUpper(cmdKong2KicManifestStyle) == "CRD" { + outputFileFormat = file.KIC_JSON_CRD + } else if strings.ToUpper(cmdKong2KicOutputFormat) == "JSON" && strings.ToUpper(cmdKong2KicManifestStyle) == "ANNOTATION" { + outputFileFormat = file.KIC_JSON_ANNOTATION + } else if strings.ToUpper(cmdKong2KicOutputFormat) == "YAML" && strings.ToUpper(cmdKong2KicManifestStyle) == "CRD" { + outputFileFormat = file.KIC_YAML_CRD + } else if strings.ToUpper(cmdKong2KicOutputFormat) == "YAML" && strings.ToUpper(cmdKong2KicManifestStyle) == "ANNOTATION" { + outputFileFormat = file.KIC_YAML_ANNOTATION + } else { + return fmt.Errorf("invalid combination of output format and manifest style") + } + + err = file.WriteContentToFile(outputContent, cmdKong2KicOutputFilename, outputFileFormat) + + if err != nil { + return fmt.Errorf("failed converting Kong to Ingress '%s'; %w", cmdKong2KicInputFilename, err) + } + + return nil +} + +// +// +// Define the CLI data for the openapi2kong command +// +// + +func newKong2KicCmd() *cobra.Command { + kong2KicCmd := &cobra.Command{ + Use: "kong2kic", + Short: "Convert Kong configuration files to Kong Ingress Controller (KIC) manifests", + Long: `Convert Kong configuration files to Kong Ingress Controller (KIC) manifests. + +Manifests can be generated using annotations in Ingress and Service objects (recommended) or +using the KongIngress CRD. Output in YAML or JSON format.`, + RunE: executeKong2Kic, + Args: cobra.NoArgs, + } + + kong2KicCmd.Flags().StringVarP(&cmdKong2KicInputFilename, "input-file", "i", "-", + "Kong spec file to process. Use - to read from stdin.") + kong2KicCmd.Flags().StringVarP(&cmdKong2KicOutputFilename, "output-file", "o", "-", + "Output file to write. Use - to write to stdout.") + kong2KicCmd.Flags().StringVar(&cmdKong2KicManifestStyle, "style", "annotation", + "Generate manifests with annotations in Service and Ingress, or using the KongIngress CRD: annotation or crd.") + kong2KicCmd.Flags().StringVarP(&cmdKong2KicOutputFormat, "format", "f", "yaml", + "output file format: json or yaml.") + //kong2KicCmd.Flags().StringVarP(&cmdKong2KicApi, "api", "a", "ingress", "[ingress|gateway]") + + return kong2KicCmd +} diff --git a/cmd/file_listtags.go b/cmd/file_listtags.go new file mode 100644 index 000000000..b94ee2794 --- /dev/null +++ b/cmd/file_listtags.go @@ -0,0 +1,94 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/tags" + "github.com/spf13/cobra" +) + +const PlainOutputFormat = "PLAIN" + +var ( + cmdListTagsInputFilename string + cmdListTagsOutputFilename string + cmdListTagsOutputFormat string + cmdListTagsSelectors []string +) + +// Executes the CLI command "list-tags" +func executeListTags(cmd *cobra.Command, _ []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + cmdListTagsOutputFormat = strings.ToUpper(cmdListTagsOutputFormat) + + // do the work: read/list-tags/write + data, err := filebasics.DeserializeFile(cmdListTagsInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdListTagsInputFilename, err) + } + + tagger := tags.Tagger{} + tagger.SetData(data) + err = tagger.SetSelectors(cmdListTagsSelectors) + if err != nil { + return fmt.Errorf("failed to set selectors; %w", err) + } + list, err := tagger.ListTags() + if err != nil { + return fmt.Errorf("failed to list tags; %w", err) + } + + if cmdListTagsOutputFormat == PlainOutputFormat { + // return as a plain text format, unix style; line separated + result := []byte(strings.Join(list, "\n")) + return filebasics.WriteFile(cmdListTagsOutputFilename, result) + } + // return as yaml/json, create an object containing only a tags-array + result := make(map[string]interface{}) + result["tags"] = list + return filebasics.WriteSerializedFile( + cmdListTagsOutputFilename, + result, + filebasics.OutputFormat(cmdListTagsOutputFormat)) +} + +// +// +// Define the CLI data for the list-tags command +// +// + +func newListTagsCmd() *cobra.Command { + ListTagsCmd := &cobra.Command{ + Use: "list-tags [flags]", + Short: "List current tags from objects in a decK file", + Long: `List current tags from objects in a decK file. + +The tags are collected from all objects that match the selector expressions. If no +selectors are given, all Kong entities will be scanned.`, + RunE: executeListTags, + Args: cobra.NoArgs, + } + + ListTagsCmd.Flags().StringVarP(&cmdListTagsInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + ListTagsCmd.Flags().StringArrayVar(&cmdListTagsSelectors, "selector", []string{}, + "JSON path expression to select objects to scan for tags.\n"+ + "Defaults to all Kong entities. Repeat for multiple selectors.") + ListTagsCmd.Flags().StringVarP(&cmdListTagsOutputFilename, "output-file", "o", "-", + "Output file to write to. Use - to write to stdout.") + ListTagsCmd.Flags().StringVarP(&cmdListTagsOutputFormat, "format", "", PlainOutputFormat, + "Output format: "+string(filebasics.OutputFormatJSON)+", "+string(filebasics.OutputFormatYaml)+ + ", or "+string(PlainOutputFormat)) + + return ListTagsCmd +} diff --git a/cmd/file_merge.go b/cmd/file_merge.go new file mode 100644 index 000000000..ce7736d61 --- /dev/null +++ b/cmd/file_merge.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "log" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/merge" + "github.com/spf13/cobra" +) + +var ( + cmdMergeOutputFilename string + cmdMergeOutputFormat string +) + +// Executes the CLI command "merge" +func executeMerge(cmd *cobra.Command, args []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + // do the work: read/merge + merged, info, err := merge.Files(args) + if err != nil { + return err + } + + historyEntry := deckformat.HistoryNewEntry("merge") + historyEntry["output"] = cmdMergeOutputFilename + historyEntry["files"] = info + deckformat.HistoryClear(merged) + deckformat.HistoryAppend(merged, historyEntry) + + return filebasics.WriteSerializedFile( + cmdMergeOutputFilename, + merged, + filebasics.OutputFormat(cmdMergeOutputFormat)) +} + +// +// +// Define the CLI data for the merge command +// +// + +func newMergeCmd() *cobra.Command { + mergeCmd := &cobra.Command{ + Use: "merge [flags] filename [...filename]", + Short: "Merge multiple decK files into one", + Long: `Merge multiple decK files into one. + +The files can be in either JSON or YAML format. Merges all top-level arrays by +concatenating them. Any other keys are copied. The files are processed in the order +provided. + +Doesn't perform any checks on content, e.g. duplicates, or any validations. + +If the input files are not compatible, returns an error. Compatibility is +determined by the '_transform' and '_format_version' fields.`, + RunE: executeMerge, + Args: cobra.MinimumNArgs(1), + } + + mergeCmd.Flags().StringVarP(&cmdMergeOutputFilename, "output-file", "o", "-", + "Output file to write to. Use - to write to stdout.") + mergeCmd.Flags().StringVarP(&cmdMergeOutputFormat, "format", "", "yaml", "output format: yaml or json") + + return mergeCmd +} diff --git a/cmd/file_openapi2kong.go b/cmd/file_openapi2kong.go new file mode 100644 index 000000000..9f5d6cb87 --- /dev/null +++ b/cmd/file_openapi2kong.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/openapi2kong" + "github.com/spf13/cobra" +) + +var ( + cmdO2KinputFilename string + cmdO2KoutputFilename string + cmdO2KdocName string + cmdO2KoutputFormat string + cmdO2KentityTags []string +) + +// Executes the CLI command "openapi2kong" +func executeOpenapi2Kong(cmd *cobra.Command, _ []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + if len(cmdO2KentityTags) == 0 { + cmdO2KentityTags = nil + } + + cmdO2KoutputFormat = strings.ToUpper(cmdO2KoutputFormat) + + options := openapi2kong.O2kOptions{ + Tags: cmdO2KentityTags, + DocName: cmdO2KdocName, + } + + trackInfo := deckformat.HistoryNewEntry("openapi2kong") + trackInfo["input"] = cmdO2KinputFilename + trackInfo["output"] = cmdO2KoutputFilename + trackInfo["uuid-base"] = cmdO2KdocName + + // do the work: read/convert/write + content, err := filebasics.ReadFile(cmdO2KinputFilename) + if err != nil { + return err + } + result, err := openapi2kong.Convert(content, options) + if err != nil { + return fmt.Errorf("failed converting OpenAPI spec '%s'; %w", cmdO2KinputFilename, err) + } + deckformat.HistoryAppend(result, trackInfo) + return filebasics.WriteSerializedFile(cmdO2KoutputFilename, result, filebasics.OutputFormat(cmdO2KoutputFormat)) +} + +// +// +// Define the CLI data for the openapi2kong command +// +// + +func newOpenapi2KongCmd() *cobra.Command { + openapi2kongCmd := &cobra.Command{ + Use: "openapi2kong", + Short: "Convert OpenAPI files to Kong's decK format", + Long: `Convert OpenAPI files to Kong's decK format. + +The example file at https://github.com/Kong/go-apiops/blob/main/docs/learnservice_oas.yaml +has extensive annotations explaining the conversion process, as well as all supported +custom annotations (x-kong-... directives).`, + RunE: executeOpenapi2Kong, + Args: cobra.NoArgs, + } + + openapi2kongCmd.Flags().StringVarP(&cmdO2KinputFilename, "spec", "s", "-", + "OpenAPI spec file to process. Use - to read from stdin.") + openapi2kongCmd.Flags().StringVarP(&cmdO2KoutputFilename, "output-file", "o", "-", + "Output file to write. Use - to write to stdout.") + openapi2kongCmd.Flags().StringVarP(&cmdO2KoutputFormat, "format", "", "yaml", "output format: yaml or json") + openapi2kongCmd.Flags().StringVarP(&cmdO2KdocName, "uuid-base", "", "", + "The unique base-string for uuid-v5 generation of entity IDs. If omitted,\n"+ + "uses the root-level \"x-kong-name\" directive, or falls back to 'info.title'.)") + openapi2kongCmd.Flags().StringSliceVar(&cmdO2KentityTags, "select-tag", nil, + "Select tags to apply to all entities. If omitted, uses the \"x-kong-tags\"\n"+ + "directive from the file.") + + return openapi2kongCmd +} diff --git a/cmd/file_patch.go b/cmd/file_patch.go new file mode 100644 index 000000000..af99bf55b --- /dev/null +++ b/cmd/file_patch.go @@ -0,0 +1,177 @@ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/jsonbasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/patch" + "github.com/spf13/cobra" +) + +var ( + cmdPatchInputFilename string + cmdPatchOutputFilename string + cmdPatchOutputFormat string + cmdPatchValues []string + cmdPatchSelectors []string +) + +// Executes the CLI command "patch" +func executePatch(cmd *cobra.Command, args []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + cmdPatchOutputFormat = strings.ToUpper(cmdPatchOutputFormat) + + var valuesPatch patch.DeckPatch + { + var err error + valuesPatch.SelectorSources = cmdPatchSelectors + valuesPatch.ObjValues, valuesPatch.Remove, err = patch.ValidateValuesFlags(cmdPatchValues) + if err != nil { + return fmt.Errorf("failed parsing '--value' entry; %w", err) + } + } + + patchFiles := make([]patch.DeckPatchFile, 0) + { + for _, filename := range args { + var patchfile patch.DeckPatchFile + err := patchfile.ParseFile(filename) + if err != nil { + return fmt.Errorf("failed to parse '%s': %w", filename, err) + } + patchFiles = append(patchFiles, patchfile) + } + } + + trackInfo := deckformat.HistoryNewEntry("patch") + trackInfo["input"] = cmdPatchInputFilename + trackInfo["output"] = cmdPatchOutputFilename + if (len(valuesPatch.ObjValues) + len(valuesPatch.Remove) + len(valuesPatch.ArrValues)) > 0 { + trackInfo["selector"] = valuesPatch.SelectorSources + } + if len(valuesPatch.ObjValues) != 0 { + trackInfo["values"] = valuesPatch.ObjValues + } + if len(valuesPatch.ArrValues) != 0 { + trackInfo["values"] = valuesPatch.ArrValues + } + if len(valuesPatch.Remove) != 0 { + trackInfo["remove"] = valuesPatch.Remove + } + if len(args) != 0 { + trackInfo["patchfiles"] = args + } + + // do the work; read/patch/write + data, err := filebasics.DeserializeFile(cmdPatchInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdPatchInputFilename, err) + } + deckformat.HistoryAppend(data, trackInfo) // add before patching, so patch can operate on it + + yamlNode := jsonbasics.ConvertToYamlNode(data) + + if (len(valuesPatch.ObjValues) + len(valuesPatch.Remove) + len(valuesPatch.ArrValues)) > 0 { + // apply selector + value flags + logbasics.Debug("applying value-flags") + err = valuesPatch.ApplyToNodes(yamlNode) + if err != nil { + return fmt.Errorf("failed to apply command-line values; %w", err) + } + } + + if len(args) > 0 { + // apply patch files + for i, patchFile := range patchFiles { + logbasics.Debug("applying patch-file", "file", i) + err := patchFile.Apply(yamlNode) + if err != nil { + return fmt.Errorf("failed to apply patch-file '%s'; %w", args[i], err) + } + } + } + + data = jsonbasics.ConvertToJSONobject(yamlNode) + + return filebasics.WriteSerializedFile(cmdPatchOutputFilename, data, filebasics.OutputFormat(cmdPatchOutputFormat)) +} + +// +// +// Define the CLI data for the patch command +// +// + +func newPatchCmd() *cobra.Command { + patchCmd := &cobra.Command{ + Use: "patch [flags] [...patch-files]", + Short: "Apply patches on top of a decK file", + Long: `Apply patches on top of a decK file. + +The input file is read, the patches are applied, and if successful, written +to the output file. The patches can be specified by a '--selector' and one or more +'--value' tags, or via patch files. + +When using '--selector' and '--values', the items are selected by the 'selector', +which is a JSONpath query. From the array of nodes found, only the objects are updated. +The 'values' are applied on each of the JSONObjects returned by the 'selector'. + +Objects: + +The value must be a valid JSON snippet, so use single/double quotes +appropriately. If the value is empty, the field is removed from the object. + +Examples of valid values: + + --selector="$..services[*]" --value="read_timeout:10000" + --selector="$..services[*]" --value='_comment:"comment injected by patching"' + --selector="$..services[*]" --value='_ignore:["ignore1","ignore2"]' + --selector="$..services[*]" --value='_ignore:' --value='_comment:' + + +Patch files have the following format (JSON or YAML) and can contain multiple +patches that are applied in order: + + { "_format_version": "1.0", + "patches": [ + { "selectors": [ + "$..services[*]" + ], + "values": { + "read_timeout": 10000, + "_comment": "comment injected by patching" + }, + "remove": [ "_ignore" ] + } + ] + } + +Arrays: + +If the 'values' object instead is an array, then any arrays returned by the selectors +will get the 'values' appended to them. +`, + RunE: executePatch, + } + + patchCmd.Flags().StringVarP(&cmdPatchInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + patchCmd.Flags().StringVarP(&cmdPatchOutputFilename, "output-file", "o", "-", + "Output file to write. Use - to write to stdout.") + patchCmd.Flags().StringVarP(&cmdPatchOutputFormat, "format", "", "yaml", + "Output format: yaml or json.") + patchCmd.Flags().StringArrayVarP(&cmdPatchSelectors, "selector", "", []string{}, + "json-pointer identifying element to patch. Repeat for multiple selectors.)") + patchCmd.Flags().StringArrayVarP(&cmdPatchValues, "value", "", []string{}, + "A value to set in the selected entry in format. Can be specified multiple times.") + patchCmd.MarkFlagsRequiredTogether("selector", "value") + + return patchCmd +} diff --git a/cmd/file_removetags.go b/cmd/file_removetags.go new file mode 100644 index 000000000..49eb0ff42 --- /dev/null +++ b/cmd/file_removetags.go @@ -0,0 +1,112 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/tags" + "github.com/spf13/cobra" +) + +var ( + cmdRemoveTagsKeepEmptyArrays bool + cmdRemoveTagsKeepOnlyTags bool + cmdRemoveTagsInputFilename string + cmdRemoveTagsOutputFilename string + cmdRemoveTagsOutputFormat string + cmdRemoveTagsSelectors []string +) + +// Executes the CLI command "remove-tags" +func executeRemoveTags(cmd *cobra.Command, tagsToRemove []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + cmdRemoveTagsOutputFormat = strings.ToUpper(cmdRemoveTagsOutputFormat) + + if !cmdRemoveTagsKeepOnlyTags && len(tagsToRemove) == 0 { + return fmt.Errorf("no tags to remove") + } + + // do the work: read/remove-tags/write + data, err := filebasics.DeserializeFile(cmdRemoveTagsInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdRemoveTagsInputFilename, err) + } + + tagger := tags.Tagger{} + tagger.SetData(data) + err = tagger.SetSelectors(cmdRemoveTagsSelectors) + if err != nil { + return fmt.Errorf("failed to set selectors; %w", err) + } + if cmdRemoveTagsKeepOnlyTags { + err = tagger.RemoveUnknownTags(tagsToRemove, !cmdRemoveTagsKeepEmptyArrays) + } else { + err = tagger.RemoveTags(tagsToRemove, !cmdRemoveTagsKeepEmptyArrays) + } + if err != nil { + return fmt.Errorf("failed to remove tags; %w", err) + } + data = tagger.GetData() + + trackInfo := deckformat.HistoryNewEntry("remove-tags") + trackInfo["input"] = cmdRemoveTagsInputFilename + trackInfo["output"] = cmdRemoveTagsOutputFilename + trackInfo["tags"] = tagsToRemove + trackInfo["keep-empty-array"] = cmdRemoveTagsKeepEmptyArrays + trackInfo["selectors"] = cmdRemoveTagsSelectors + deckformat.HistoryAppend(data, trackInfo) + + return filebasics.WriteSerializedFile( + cmdRemoveTagsOutputFilename, + data, + filebasics.OutputFormat(cmdRemoveTagsOutputFormat)) +} + +// +// +// Define the CLI data for the remove-tags command +// +// + +func newRemoveTagsCmd() *cobra.Command { + removeTagsCmd := &cobra.Command{ + Use: "remove-tags [flags] tag [...tag]", + Short: "Remove tags from objects in a decK file", + Long: `Remove tags from objects in a decK file. + +The listed tags are removed from all objects that match the selector expressions. +If no selectors are given, all Kong entities are selected.`, + RunE: executeRemoveTags, + Example: "# clear tags 'tag1' and 'tag2' from all services in file 'kong.yml'\n" + + "cat kong.yml | deck file remove-tags --selector='services[*]' tag1 tag2\n" + + "\n" + + "# clear all tags except 'tag1' and 'tag2' from the file 'kong.yml'\n" + + "cat kong.yml | deck file remove-tags --keep-only tag1 tag2", + } + + removeTagsCmd.Flags().BoolVar(&cmdRemoveTagsKeepEmptyArrays, "keep-empty-array", false, + "Keep empty tag arrays in output.") + removeTagsCmd.Flags().BoolVar(&cmdRemoveTagsKeepOnlyTags, "keep-only", false, + "Setting this flag will remove all tags except the ones listed.\n"+ + "If none are listed, all tags will be removed.") + removeTagsCmd.Flags().StringVarP(&cmdRemoveTagsInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + removeTagsCmd.Flags().StringArrayVar(&cmdRemoveTagsSelectors, "selector", []string{}, + "JSON path expression to select objects to remove tags from.\n"+ + "Defaults to all Kong entities. Repeat for multiple selectors.") + removeTagsCmd.Flags().StringVarP(&cmdRemoveTagsOutputFilename, "output-file", "o", "-", + "Output file to write. Use - to write to stdout.") + removeTagsCmd.Flags().StringVarP(&cmdRemoveTagsOutputFormat, "format", "", string(filebasics.OutputFormatYaml), + "Output format: "+string(filebasics.OutputFormatJSON)+" or "+string(filebasics.OutputFormatYaml)) + + return removeTagsCmd +} diff --git a/cmd/file_render.go b/cmd/file_render.go new file mode 100644 index 000000000..c5b26b138 --- /dev/null +++ b/cmd/file_render.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "strings" + + "github.com/kong/deck/convert" + "github.com/kong/deck/file" + "github.com/spf13/cobra" +) + +var ( + fileRenderCmdKongStateFile []string + fileRenderCmdKongFileOutput string + fileRenderCmdStateFormat string +) + +func executeFileRenderCmd(_ *cobra.Command, _ []string) error { + return convert.Convert( + fileRenderCmdKongStateFile, + fileRenderCmdKongFileOutput, + file.Format(strings.ToUpper(fileRenderCmdStateFormat)), + convert.FormatDistributed, + convert.FormatKongGateway3x, + true) +} + +func newFileRenderCmd() *cobra.Command { + renderCmd := &cobra.Command{ + Use: "render", + Short: "Render the configuration as Kong declarative config", + Long: ``, + Args: cobra.ArbitraryArgs, + RunE: executeFileRenderCmd, + PreRunE: func(cmd *cobra.Command, args []string) error { + fileRenderCmdKongStateFile = args + if len(fileRenderCmdKongStateFile) == 0 { + fileRenderCmdKongStateFile = []string{"-"} // default to stdin + } + return preRunSilenceEventsFlag() + }, + } + + renderCmd.Flags().StringVarP(&fileRenderCmdKongFileOutput, "output-file", "o", + "-", "file to which to write Kong's configuration."+ + "Use `-` to write to stdout.") + renderCmd.Flags().StringVar(&fileRenderCmdStateFormat, "format", + "yaml", "output file format: json or yaml.") + + return renderCmd +} diff --git a/cmd/konnect_diff.go b/cmd/konnect_diff.go index 42e91e649..429c5eecf 100644 --- a/cmd/konnect_diff.go +++ b/cmd/konnect_diff.go @@ -23,9 +23,6 @@ func newKonnectDiffCmd() *cobra.Command { Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { _ = sendAnalytics("konnect-diff", "", modeKonnect) - if konnectConfig.Address == defaultKonnectURL { - konnectConfig.Address = defaultLegacyKonnectURL - } return syncKonnect(cmd.Context(), konnectDiffCmdKongStateFile, true, konnectDiffCmdParallelism) }, diff --git a/cmd/konnect_dump.go b/cmd/konnect_dump.go index c3ddd7100..65d3f6ded 100644 --- a/cmd/konnect_dump.go +++ b/cmd/konnect_dump.go @@ -38,9 +38,6 @@ func newKonnectDumpCmd() *cobra.Command { } // get Konnect client - if konnectConfig.Address == defaultKonnectURL { - konnectConfig.Address = defaultLegacyKonnectURL - } konnectClient, err := utils.GetKonnectClient(httpClient, konnectConfig) if err != nil { return err diff --git a/cmd/konnect_ping.go b/cmd/konnect_ping.go index 06e6b3e18..31e5608ca 100644 --- a/cmd/konnect_ping.go +++ b/cmd/konnect_ping.go @@ -18,9 +18,6 @@ credentials.` + konnectAlphaState, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { _ = sendAnalytics("konnect-ping", "", modeKonnect) - if konnectConfig.Address == defaultKonnectURL { - konnectConfig.Address = defaultLegacyKonnectURL - } client, err := utils.GetKonnectClient(nil, konnectConfig) if err != nil { return err diff --git a/cmd/konnect_sync.go b/cmd/konnect_sync.go index dae3425ce..93de46fc1 100644 --- a/cmd/konnect_sync.go +++ b/cmd/konnect_sync.go @@ -15,9 +15,6 @@ to get Konnect's state in sync with the input state.` + konnectAlphaState, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { _ = sendAnalytics("konnect-sync", "", modeKonnect) - if konnectConfig.Address == defaultKonnectURL { - konnectConfig.Address = defaultLegacyKonnectURL - } return syncKonnect(cmd.Context(), konnectDiffCmdKongStateFile, false, konnectDiffCmdParallelism) }, diff --git a/cmd/ping.go b/cmd/ping.go index d2a6b0e76..4a59e59dd 100644 --- a/cmd/ping.go +++ b/cmd/ping.go @@ -3,7 +3,6 @@ package cmd import ( "context" "fmt" - "net/url" "github.com/kong/deck/utils" "github.com/spf13/cobra" @@ -47,9 +46,8 @@ func pingKonnect(ctx context.Context) error { if err != nil { return err } - u, _ := url.Parse(konnectConfig.Address) // authenticate with konnect - res, err := authenticate(ctx, konnectClient, u.Host, konnectConfig) + res, err := authenticate(ctx, konnectClient, konnectConfig) if err != nil { return fmt.Errorf("authenticating with Konnect: %w", err) } diff --git a/cmd/reset.go b/cmd/reset.go index f9b7574ce..d0a68780e 100644 --- a/cmd/reset.go +++ b/cmd/reset.go @@ -12,6 +12,7 @@ var ( resetCmdForce bool resetWorkspace string resetAllWorkspaces bool + resetJSONOutput bool ) // newResetCmd represents the reset command @@ -59,8 +60,16 @@ By default, this command will ask for confirmation.`, if err != nil { return fmt.Errorf("reading Kong version: %w", err) } + parsedKongVersion, err := utils.ParseKongVersion(kongVersion) + if err != nil { + return fmt.Errorf("parsing Kong version: %w", err) + } _ = sendAnalytics("reset", kongVersion, mode) + if utils.Kong340Version.LTE(parsedKongVersion) { + dumpConfig.IsConsumerGroupScopedPluginSupported = true + } + var workspaces []string // Kong OSS or default workspace if !resetAllWorkspaces && resetWorkspace == "" { @@ -99,7 +108,7 @@ By default, this command will ask for confirmation.`, if err != nil { return err } - _, err = performDiff(ctx, currentState, targetState, false, 10, 0, wsClient, false) + _, err = performDiff(ctx, currentState, targetState, false, 10, 0, wsClient, false, resetJSONOutput) if err != nil { return err } @@ -128,6 +137,8 @@ By default, this command will ask for confirmation.`, false, "reset only the RBAC resources (Kong Enterprise only).") resetCmd.Flags().BoolVar(&dumpConfig.SkipCACerts, "skip-ca-certificates", false, "do not reset CA certificates.") + resetCmd.Flags().BoolVar(&resetJSONOutput, "json-output", + false, "generate command execution report in a JSON format") return resetCmd } diff --git a/cmd/root.go b/cmd/root.go index a14748744..1fda80d3b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,6 +11,7 @@ import ( "github.com/fatih/color" "github.com/kong/deck/utils" + "github.com/kong/go-apiops/deckformat" homedir "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -102,8 +103,7 @@ It can be used to export, import, or sync entities to Kong.`, rootCmd.PersistentFlags().Int("verbose", 0, "Enable verbose logging levels\n"+ - "Setting this value to 2 outputs all HTTP requests/responses\n"+ - "between decK and Kong.") + "Sets the verbosity level of log output (higher is more verbose).") viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose")) @@ -187,7 +187,7 @@ It can be used to export, import, or sync entities to Kong.`, rootCmd.PersistentFlags().Lookup("konnect-token")) rootCmd.PersistentFlags().String("konnect-token-file", "", - "File containing the Peronsal Access Token to your Konnect account.") + "File containing the Personal Access Token to your Konnect account.") viper.BindPFlag("konnect-token-file", rootCmd.PersistentFlags().Lookup("konnect-token-file")) @@ -214,6 +214,19 @@ It can be used to export, import, or sync entities to Kong.`, rootCmd.AddCommand(newConvertCmd()) rootCmd.AddCommand(newCompletionCmd()) rootCmd.AddCommand(newKonnectCmd()) + { + fileCmd := newAddFileCmd() + rootCmd.AddCommand(fileCmd) + fileCmd.AddCommand(newAddPluginsCmd()) + fileCmd.AddCommand(newAddTagsCmd()) + fileCmd.AddCommand(newListTagsCmd()) + fileCmd.AddCommand(newRemoveTagsCmd()) + fileCmd.AddCommand(newMergeCmd()) + fileCmd.AddCommand(newPatchCmd()) + fileCmd.AddCommand(newOpenapi2KongCmd()) + fileCmd.AddCommand(newFileRenderCmd()) + fileCmd.AddCommand(newKong2KicCmd()) + } return rootCmd } @@ -380,3 +393,8 @@ func extendHeaders(headers []string) []string { headers = append(headers, userAgentHeader) return headers } + +func init() { + // set version and commit hash to report in the go-apiops library + deckformat.ToolVersionSet("decK", VERSION, COMMIT) +} diff --git a/cmd/sync.go b/cmd/sync.go index 856577d21..e8d6d5e1b 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -10,6 +10,7 @@ var ( syncCmdParallelism int syncCmdDBUpdateDelay int syncWorkspace string + syncJSONOutput bool ) // newSyncCmd represents the sync command @@ -24,7 +25,7 @@ to get Kong's state in sync with the input state.`, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { return syncMain(cmd.Context(), syncCmdKongStateFile, false, - syncCmdParallelism, syncCmdDBUpdateDelay, syncWorkspace) + syncCmdParallelism, syncCmdDBUpdateDelay, syncWorkspace, syncJSONOutput) }, PreRunE: func(cmd *cobra.Command, args []string) error { if len(syncCmdKongStateFile) == 0 { @@ -62,6 +63,8 @@ to get Kong's state in sync with the input state.`, "See `db_update_propagation` in kong.conf.") syncCmd.Flags().BoolVar(&dumpConfig.SkipCACerts, "skip-ca-certificates", false, "do not sync CA certificates.") + syncCmd.Flags().BoolVar(&syncJSONOutput, "json-output", + false, "generate command execution report in a JSON format") addSilenceEventsFlag(syncCmd.Flags()) return syncCmd } diff --git a/cmd/validate.go b/cmd/validate.go index 2035d9933..d44f4d000 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -19,6 +19,7 @@ var ( validateOnline bool validateWorkspace string validateParallelism int + validateJSONOutput bool ) // newValidateCmd represents the diff command @@ -43,7 +44,7 @@ this command unless --online flag is used. _ = sendAnalytics("validate", "", mode) // read target file // this does json schema validation as well - targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile) + targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile, false) if err != nil { return err } @@ -107,6 +108,8 @@ this command unless --online flag is used. "This takes precedence over _workspace fields in state files.") validateCmd.Flags().IntVar(&validateParallelism, "parallelism", 10, "Maximum number of concurrent requests to Kong.") + validateCmd.Flags().BoolVar(&validateJSONOutput, "json-output", + false, "generate command execution report in a JSON format") if err := ensureGetAllMethods(); err != nil { panic(err.Error()) @@ -139,7 +142,7 @@ func getKongClient(ctx context.Context, targetContent *file.Content) (*kong.Clie workspaceName := validateWorkspace if validateWorkspace != "" { // check if workspace exists - workspaceName := getWorkspaceName(validateWorkspace, targetContent) + workspaceName := getWorkspaceName(validateWorkspace, targetContent, validateJSONOutput) workspaceExists, err := workspaceExists(ctx, rootConfig, workspaceName) if err != nil { return nil, err diff --git a/convert/.gitignore b/convert/.gitignore new file mode 100644 index 000000000..c5bf9d03a --- /dev/null +++ b/convert/.gitignore @@ -0,0 +1 @@ +output.yaml diff --git a/convert/convert.go b/convert/convert.go index 49843efd9..2e1ec0ce1 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -1,11 +1,15 @@ package convert import ( + "context" "fmt" "strings" + "github.com/blang/semver/v4" "github.com/kong/deck/cprint" + "github.com/kong/deck/dump" "github.com/kong/deck/file" + "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" ) @@ -13,6 +17,8 @@ import ( type Format string const ( + // FormatDistributed represents the Deck configuration format. + FormatDistributed Format = "distributed" // FormatKongGateway represents the Kong gateway format. FormatKongGateway Format = "kong-gateway" // FormatKonnect represents the Konnect format. @@ -37,42 +43,61 @@ func ParseFormat(key string) (Format, error) { return FormatKongGateway2x, nil case FormatKongGateway3x: return FormatKongGateway3x, nil + case FormatDistributed: + return FormatDistributed, nil default: return "", fmt.Errorf("invalid format: '%v'", key) } } -func Convert(inputFilename, outputFilename string, from, to Format) error { - var ( - outputContent *file.Content - err error - ) +func Convert( + inputFilenames []string, + outputFilename string, + outputFormat file.Format, + from Format, + to Format, + mockEnvVars bool, +) error { + var outputContent *file.Content - inputContent, err := file.GetContentFromFiles([]string{inputFilename}) + inputContent, err := file.GetContentFromFiles(inputFilenames, mockEnvVars) if err != nil { return err } switch { case from == FormatKongGateway && to == FormatKonnect: + if len(inputFilenames) > 1 { + return fmt.Errorf("only one input file can be provided when converting from Kong to Konnect format") + } outputContent, err = convertKongGatewayToKonnect(inputContent) if err != nil { return err } + case from == FormatKongGateway2x && to == FormatKongGateway3x: - outputContent, err = convertKongGateway2xTo3x(inputContent, inputFilename) + if len(inputFilenames) > 1 { + return fmt.Errorf("only one input file can be provided when converting from Kong 2.x to Kong 3.x format") + } + outputContent, err = convertKongGateway2xTo3x(inputContent, inputFilenames[0]) if err != nil { return err } + + case from == FormatDistributed && to == FormatKongGateway, + from == FormatDistributed && to == FormatKongGateway2x, + from == FormatDistributed && to == FormatKongGateway3x: + outputContent, err = convertDistributedToKong(inputContent, outputFilename, outputFormat, to) + if err != nil { + return err + } + default: return fmt.Errorf("cannot convert from '%s' to '%s' format", from, to) } - err = file.WriteContentToFile(outputContent, outputFilename, file.YAML) - if err != nil { - return err - } - return nil + err = file.WriteContentToFile(outputContent, outputFilename, outputFormat) + return err } func convertKongGateway2xTo3x(input *file.Content, filename string) (*file.Content, error) { @@ -195,3 +220,43 @@ func removeServiceName(service *file.FService) *file.FService { serviceCopy.ID = kong.String(utils.UUID()) return serviceCopy } + +// convertDistributedToKong is used to convert one or many distributed format +// files to create one Kong Gateway declarative config. It also leverages some +// deck features like the defaults/centralized plugin configurations. +func convertDistributedToKong( + targetContent *file.Content, + outputFilename string, + format file.Format, + kongFormat Format, +) (*file.Content, error) { + var version semver.Version + + switch kongFormat { //nolint:exhaustive + case FormatKongGateway, + FormatKongGateway3x: + version = semver.Version{Major: 3, Minor: 0} + case FormatKongGateway2x: + version = semver.Version{Major: 2, Minor: 8} + } + + s, _ := state.NewKongState() + rawState, err := file.Get(context.Background(), targetContent, file.RenderConfig{ + CurrentState: s, + KongVersion: version, + }, dump.Config{}, nil) + if err != nil { + return nil, err + } + targetState, err := state.Get(rawState) + if err != nil { + return nil, err + } + + // file.KongStateToContent calls file.WriteContentToFile + return file.KongStateToContent(targetState, file.WriteConfig{ + Filename: outputFilename, + FileFormat: format, + KongVersion: version.String(), + }) +} diff --git a/convert/convert_test.go b/convert/convert_test.go index ab341dfc4..a025a7e72 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -60,6 +60,7 @@ func TestParseFormat(t *testing.T) { want: "", wantErr: true, }, + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -158,9 +159,12 @@ func zeroOutID(sp file.FServicePackage) file.FServicePackage { func Test_Convert(t *testing.T) { type args struct { inputFilename string + inputFilenames []string outputFilename string fromFormat Format toFormat Format + disableMocks bool + envVars map[string]string expectedOutputFilename string } tests := []struct { @@ -237,21 +241,102 @@ func Test_Convert(t *testing.T) { }, wantErr: false, }, + { + name: "converts from distributed to kong gateway (no deck specific fields)", + args: args{ + inputFilename: "testdata/5/input.yaml", + outputFilename: "testdata/5/output.yaml", + expectedOutputFilename: "testdata/5/output-expected.yaml", + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + }, + wantErr: false, + }, + { + name: "converts from distributed to kong gateway with defaults", + args: args{ + inputFilename: "testdata/6/input.yaml", + outputFilename: "testdata/6/output.yaml", + expectedOutputFilename: "testdata/6/output-expected.yaml", + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + }, + wantErr: false, + }, + { + name: "converts from distributed to kong gateway with multiple files", + args: args{ + inputFilenames: []string{"testdata/7/input-1.yaml", "testdata/7/input-2.yaml"}, + outputFilename: "testdata/7/output.yaml", + expectedOutputFilename: "testdata/7/output-expected.yaml", + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + }, + wantErr: false, + }, + { + name: "converts from distributed to kong gateway with env variables", + args: args{ + inputFilenames: []string{"testdata/8/input.yaml"}, + outputFilename: "testdata/8/output.yaml", + expectedOutputFilename: "testdata/8/output-expected.yaml", + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + disableMocks: true, + envVars: map[string]string{ + "DECK_MOCKBIN_HOST": "mockbin.org", + "DECK_MOCKBIN_ENABLED": "true", + "DECK_WRITE_TIMEOUT": "777", + "DECK_FOO_FLOAT": "666", + }, + }, + wantErr: false, + }, + { + name: "converts from distributed to kong gateway with env variables (mocked)", + args: args{ + inputFilenames: []string{"testdata/9/input.yaml"}, + outputFilename: "testdata/9/output.yaml", + expectedOutputFilename: "testdata/9/output-expected.yaml", + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + disableMocks: false, + }, + wantErr: false, + }, + { + name: "errors from distributed to kong gateway with env variables not set", + args: args{ + inputFilenames: []string{"testdata/9/input.yaml"}, + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + disableMocks: true, + }, + wantErr: true, + }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := Convert(tt.args.inputFilename, tt.args.outputFilename, tt.args.fromFormat, - tt.args.toFormat) + inputFiles := tt.args.inputFilenames + if tt.args.inputFilename != "" { + inputFiles = []string{tt.args.inputFilename} + } + for k, v := range tt.args.envVars { + t.Setenv(k, v) + } + err := Convert(inputFiles, tt.args.outputFilename, file.YAML, tt.args.fromFormat, + tt.args.toFormat, !tt.args.disableMocks) if (err != nil) != tt.wantErr { t.Errorf("Convert() error = %v, wantErr %v", err, tt.wantErr) } if err == nil { - got, err := file.GetContentFromFiles([]string{tt.args.outputFilename}) + got, err := file.GetContentFromFiles([]string{tt.args.outputFilename}, !tt.args.disableMocks) if err != nil { t.Errorf("failed to read output file: %v", err) } - want, err := file.GetContentFromFiles([]string{tt.args.expectedOutputFilename}) + want, err := file.GetContentFromFiles([]string{tt.args.expectedOutputFilename}, !tt.args.disableMocks) if err != nil { t.Errorf("failed to read output file: %v", err) } @@ -385,4 +470,4 @@ func Test_convertKongGatewayToKonnect(t *testing.T) { } }) } -} +} \ No newline at end of file diff --git a/convert/testdata/3/output.yaml b/convert/testdata/3/output.yaml deleted file mode 100644 index eafd39703..000000000 --- a/convert/testdata/3/output.yaml +++ /dev/null @@ -1,10 +0,0 @@ -_format_version: "3.0" -services: -- host: mockbin.org - name: svc1 - path: /status/200 - routes: - - name: r1 - paths: - - ~/status/\d+ - - ~/code/\d+ diff --git a/convert/testdata/4/output.yaml b/convert/testdata/4/output.yaml deleted file mode 100644 index eafd39703..000000000 --- a/convert/testdata/4/output.yaml +++ /dev/null @@ -1,10 +0,0 @@ -_format_version: "3.0" -services: -- host: mockbin.org - name: svc1 - path: /status/200 - routes: - - name: r1 - paths: - - ~/status/\d+ - - ~/code/\d+ diff --git a/convert/testdata/5/input.yaml b/convert/testdata/5/input.yaml new file mode 100644 index 000000000..fafdb57d5 --- /dev/null +++ b/convert/testdata/5/input.yaml @@ -0,0 +1,35 @@ +_format_version: "3.0" +services: +- name: svc1 + host: mockbin.org + tags: + - team-svc1 + routes: + - name: r1 + https_redirect_status_code: 301 + paths: + - /r1 +- name: svc2 + host: mockbin.org + routes: + - name: r2 + https_redirect_status_code: 301 + paths: + - /r2 +- name: svc3 + host: mockbin.org + port: 80 + routes: + - name: r3 + https_redirect_status_code: 301 + paths: + - /r3 + methods: + - GET +plugins: +- name: prometheus + enabled: true + run_on: first + protocols: + - http + - https diff --git a/convert/testdata/5/output-expected.yaml b/convert/testdata/5/output-expected.yaml new file mode 100644 index 000000000..ecd2702a3 --- /dev/null +++ b/convert/testdata/5/output-expected.yaml @@ -0,0 +1,65 @@ +_format_version: "3.0" +plugins: +- enabled: true + name: prometheus + protocols: + - http + - https + run_on: first +services: +- connect_timeout: 60000 + host: mockbin.org + name: svc1 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r1 + paths: + - /r1 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + tags: + - team-svc1 + write_timeout: 60000 +- connect_timeout: 60000 + host: mockbin.org + name: svc2 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r2 + paths: + - /r2 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + write_timeout: 60000 +- connect_timeout: 60000 + host: mockbin.org + name: svc3 + port: 80 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + methods: + - GET + name: r3 + paths: + - /r3 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + write_timeout: 60000 diff --git a/convert/testdata/6/input.yaml b/convert/testdata/6/input.yaml new file mode 100644 index 000000000..fed85d987 --- /dev/null +++ b/convert/testdata/6/input.yaml @@ -0,0 +1,41 @@ +_format_version: "3.0" +_info: + defaults: + service: + connect_timeout: 30000 + write_timeout: 30000 + route: + protocols: + - https + https_redirect_status_code: 301 +services: +- name: svc1 + host: mockbin.org + tags: + - team-svc1 + routes: + - name: r1 + paths: + - /r1 +- name: svc2 + host: mockbin.org + routes: + - name: r2 + paths: + - /r2 +- name: svc3 + host: mockbin.org + port: 80 + routes: + - name: r3 + paths: + - /r3 + methods: + - GET +plugins: +- name: prometheus + enabled: true + run_on: first + protocols: + - http + - https diff --git a/convert/testdata/6/output-expected.yaml b/convert/testdata/6/output-expected.yaml new file mode 100644 index 000000000..bbd6297c7 --- /dev/null +++ b/convert/testdata/6/output-expected.yaml @@ -0,0 +1,62 @@ +_format_version: "3.0" +plugins: +- enabled: true + name: prometheus + protocols: + - http + - https + run_on: first +services: +- connect_timeout: 30000 + host: mockbin.org + name: svc1 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r1 + paths: + - /r1 + preserve_host: false + protocols: + - https + regex_priority: 0 + strip_path: true + tags: + - team-svc1 + write_timeout: 30000 +- connect_timeout: 30000 + host: mockbin.org + name: svc2 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r2 + paths: + - /r2 + preserve_host: false + protocols: + - https + regex_priority: 0 + strip_path: true + write_timeout: 30000 +- connect_timeout: 30000 + host: mockbin.org + name: svc3 + port: 80 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + methods: + - GET + name: r3 + paths: + - /r3 + preserve_host: false + protocols: + - https + regex_priority: 0 + strip_path: true + write_timeout: 30000 diff --git a/convert/testdata/7/input-1.yaml b/convert/testdata/7/input-1.yaml new file mode 100644 index 000000000..9b867b7ef --- /dev/null +++ b/convert/testdata/7/input-1.yaml @@ -0,0 +1,25 @@ +_format_version: "3.0" +services: +- name: svc1 + host: mockbin.org + tags: + - team-svc1 + routes: + - name: r1 + https_redirect_status_code: 301 + paths: + - /r1 +- name: svc2 + host: mockbin.org + routes: + - name: r2 + https_redirect_status_code: 301 + paths: + - /r2 +plugins: +- name: prometheus + enabled: true + run_on: first + protocols: + - http + - https diff --git a/convert/testdata/7/input-2.yaml b/convert/testdata/7/input-2.yaml new file mode 100644 index 000000000..609ab6d4f --- /dev/null +++ b/convert/testdata/7/input-2.yaml @@ -0,0 +1,12 @@ +_format_version: "3.0" +services: +- name: svc3 + host: mockbin.org + port: 80 + routes: + - name: r3 + https_redirect_status_code: 301 + paths: + - /r3 + methods: + - GET diff --git a/convert/testdata/7/output-expected.yaml b/convert/testdata/7/output-expected.yaml new file mode 100644 index 000000000..ecd2702a3 --- /dev/null +++ b/convert/testdata/7/output-expected.yaml @@ -0,0 +1,65 @@ +_format_version: "3.0" +plugins: +- enabled: true + name: prometheus + protocols: + - http + - https + run_on: first +services: +- connect_timeout: 60000 + host: mockbin.org + name: svc1 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r1 + paths: + - /r1 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + tags: + - team-svc1 + write_timeout: 60000 +- connect_timeout: 60000 + host: mockbin.org + name: svc2 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r2 + paths: + - /r2 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + write_timeout: 60000 +- connect_timeout: 60000 + host: mockbin.org + name: svc3 + port: 80 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + methods: + - GET + name: r3 + paths: + - /r3 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + write_timeout: 60000 diff --git a/convert/testdata/8/input.yaml b/convert/testdata/8/input.yaml new file mode 100644 index 000000000..fbe43b53f --- /dev/null +++ b/convert/testdata/8/input.yaml @@ -0,0 +1,9 @@ +services: +- name: svc1 + host: ${{ env "DECK_MOCKBIN_HOST" }} + enabled: ${{ env "DECK_MOCKBIN_ENABLED" | toBool }} + write_timeout: ${{ env "DECK_WRITE_TIMEOUT" | toInt }} +plugins: +- config: + foo: ${{ env "DECK_FOO_FLOAT" | toFloat }} + name: foofloat diff --git a/convert/testdata/8/output-expected.yaml b/convert/testdata/8/output-expected.yaml new file mode 100644 index 000000000..ac8206034 --- /dev/null +++ b/convert/testdata/8/output-expected.yaml @@ -0,0 +1,13 @@ +_format_version: "3.0" +plugins: +- config: + foo: 666 + name: foofloat +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + protocol: http + read_timeout: 60000 + write_timeout: 777 diff --git a/convert/testdata/9/input.yaml b/convert/testdata/9/input.yaml new file mode 100644 index 000000000..fbe43b53f --- /dev/null +++ b/convert/testdata/9/input.yaml @@ -0,0 +1,9 @@ +services: +- name: svc1 + host: ${{ env "DECK_MOCKBIN_HOST" }} + enabled: ${{ env "DECK_MOCKBIN_ENABLED" | toBool }} + write_timeout: ${{ env "DECK_WRITE_TIMEOUT" | toInt }} +plugins: +- config: + foo: ${{ env "DECK_FOO_FLOAT" | toFloat }} + name: foofloat diff --git a/convert/testdata/9/output-expected.yaml b/convert/testdata/9/output-expected.yaml new file mode 100644 index 000000000..8bc7ffd48 --- /dev/null +++ b/convert/testdata/9/output-expected.yaml @@ -0,0 +1,13 @@ +_format_version: "3.0" +plugins: +- config: + foo: 42 + name: foofloat +services: +- connect_timeout: 60000 + enabled: false + host: DECK_MOCKBIN_HOST + name: svc1 + protocol: http + read_timeout: 60000 + write_timeout: 42 diff --git a/cprint/color.go b/cprint/color.go index 7d0a54de3..e5c9d8d2a 100644 --- a/cprint/color.go +++ b/cprint/color.go @@ -54,6 +54,7 @@ var ( createPrintln = color.New(color.FgGreen).PrintlnFunc() deletePrintln = color.New(color.FgRed).PrintlnFunc() updatePrintln = color.New(color.FgYellow).PrintlnFunc() + bluePrintln = color.New(color.BgBlue).PrintlnFunc() // CreatePrintln is fmt.Println with red as foreground color. CreatePrintln = func(a ...interface{}) { @@ -69,4 +70,8 @@ var ( UpdatePrintln = func(a ...interface{}) { conditionalPrintln(updatePrintln, a...) } + + BluePrintLn = func(a ...interface{}) { + conditionalPrintln(bluePrintln, a...) + } ) diff --git a/diff/diff.go b/diff/diff.go index 85ce902b2..b26eec8f0 100644 --- a/diff/diff.go +++ b/diff/diff.go @@ -19,6 +19,32 @@ import ( "github.com/kong/go-kong/kong" ) +type EntityState struct { + Name string `json:"name"` + Kind string `json:"kind"` + Body any `json:"body"` +} + +type Summary struct { + Creating int32 `json:"creating"` + Updating int32 `json:"updating"` + Deleting int32 `json:"deleting"` + Total int32 `json:"total"` +} + +type JSONOutputObject struct { + Changes EntityChanges `json:"changes"` + Summary Summary `json:"summary"` + Warnings []string `json:"warnings"` + Errors []string `json:"errors"` +} + +type EntityChanges struct { + Creating []EntityState `json:"creating"` + Updating []EntityState `json:"updating"` + Deleting []EntityState `json:"deleting"` +} + var errEnqueueFailed = errors.New("failed to queue event") func defaultBackOff() backoff.BackOff { @@ -438,13 +464,15 @@ func generateDiffString(e crud.Event, isDelete bool, noMaskValues bool) (string, return "", err } if !noMaskValues { - diffString = maskEnvVarValue(diffString) + diffString = MaskEnvVarValue(diffString) } return diffString, err } // Solve generates a diff and walks the graph. -func (sc *Syncer) Solve(ctx context.Context, parallelism int, dry bool) (Stats, []error) { +func (sc *Syncer) Solve(ctx context.Context, parallelism int, dry bool, isJSONOut bool) (Stats, + []error, EntityChanges, +) { stats := Stats{ CreateOps: &utils.AtomicInt32Counter{}, UpdateOps: &utils.AtomicInt32Counter{}, @@ -461,22 +489,49 @@ func (sc *Syncer) Solve(ctx context.Context, parallelism int, dry bool) (Stats, } } + output := EntityChanges{ + Creating: []EntityState{}, + Updating: []EntityState{}, + Deleting: []EntityState{}, + } + errs := sc.Run(ctx, parallelism, func(e crud.Event) (crud.Arg, error) { var err error var result crud.Arg c := e.Obj.(state.ConsoleString) + objDiff := map[string]interface{}{ + "old": e.OldObj, + "new": e.Obj, + } + item := EntityState{ + Body: objDiff, + Name: c.Console(), + Kind: string(e.Kind), + } switch e.Op { case crud.Create: - sc.createPrintln("creating", e.Kind, c.Console()) + if isJSONOut { + output.Creating = append(output.Creating, item) + } else { + sc.createPrintln("creating", e.Kind, c.Console()) + } case crud.Update: diffString, err := generateDiffString(e, false, sc.noMaskValues) if err != nil { return nil, err } - sc.updatePrintln("updating", e.Kind, c.Console(), diffString) + if isJSONOut { + output.Updating = append(output.Updating, item) + } else { + sc.updatePrintln("updating", e.Kind, c.Console(), diffString) + } case crud.Delete: - sc.deletePrintln("deleting", e.Kind, c.Console()) + if isJSONOut { + output.Deleting = append(output.Deleting, item) + } else { + sc.deletePrintln("deleting", e.Kind, c.Console()) + } default: panic("unknown operation " + e.Op.String()) } @@ -498,5 +553,5 @@ func (sc *Syncer) Solve(ctx context.Context, parallelism int, dry bool) (Stats, return result, nil }) - return stats, errs + return stats, errs, output } diff --git a/diff/diff_helpers.go b/diff/diff_helpers.go index 6bbd0c768..5be1f726b 100644 --- a/diff/diff_helpers.go +++ b/diff/diff_helpers.go @@ -112,7 +112,7 @@ func parseDeckEnvVars() []EnvVar { return parsedEnvVars } -func maskEnvVarValue(diffString string) string { +func MaskEnvVarValue(diffString string) string { for _, envVar := range parseDeckEnvVars() { diffString = strings.Replace(diffString, envVar.Value, "[masked]", -1) } diff --git a/diff/diff_helpers_test.go b/diff/diff_helpers_test.go index 1088e4e5d..82f26c14f 100644 --- a/diff/diff_helpers_test.go +++ b/diff/diff_helpers_test.go @@ -168,7 +168,7 @@ func Test_MaskEnvVarsValues(t *testing.T) { for k, v := range tt.envVars { t.Setenv(k, v) } - if got := maskEnvVarValue(tt.args); got != tt.want { + if got := MaskEnvVarValue(tt.args); got != tt.want { t.Errorf("maskEnvVarValue() = %v\nwant %v", got, tt.want) } }) diff --git a/dump/dump.go b/dump/dump.go index 51fa2e7d7..1ff4c96b2 100644 --- a/dump/dump.go +++ b/dump/dump.go @@ -6,7 +6,6 @@ import ( "fmt" "net/http" - "github.com/kong/deck/konnect" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" "golang.org/x/sync/errgroup" @@ -31,6 +30,9 @@ type Config struct { // KonnectRuntimeGroup KonnectRuntimeGroup string + + // IsConsumerGroupScopedPluginSupported + IsConsumerGroupScopedPluginSupported bool } func deduplicate(stringSlice []string) []string { @@ -71,15 +73,7 @@ func getConsumerGroupsConfiguration(ctx context.Context, group *errgroup.Group, client *kong.Client, config Config, state *utils.KongRawState, ) { group.Go(func() error { - var ( - err error - consumerGroups []*kong.ConsumerGroupObject - ) - if config.KonnectRuntimeGroup != "" { - consumerGroups, err = GetAllKonnectConsumerGroups(ctx, client, config.SelectorTags) - } else { - consumerGroups, err = GetAllConsumerGroups(ctx, client, config.SelectorTags) - } + consumerGroups, err := GetAllConsumerGroups(ctx, client, config.SelectorTags) if err != nil { if kong.IsNotFoundErr(err) || kong.IsForbiddenErr(err) { return nil @@ -205,6 +199,7 @@ func getProxyConfiguration(ctx context.Context, group *errgroup.Group, plugins = excludeKonnectManagedPlugins(plugins) if config.SkipConsumers { plugins = excludeConsumersPlugins(plugins) + plugins = excludeConsumerGroupsPlugins(plugins) } state.Plugins = plugins return nil @@ -528,29 +523,6 @@ func GetAllUpstreams(ctx context.Context, return upstreams, nil } -// GetAllConsumerGroups queries Konnect for all the ConsumerGroups using client. -func GetAllKonnectConsumerGroups(ctx context.Context, - client *kong.Client, tags []string, -) ([]*kong.ConsumerGroupObject, error) { - var consumerGroupObjects []*kong.ConsumerGroupObject - opt := newOpt(tags) - cgs, err := konnect.ListAllConsumerGroups(ctx, client, opt.Tags) - if err != nil { - return nil, err - } - if err := ctx.Err(); err != nil { - return nil, err - } - for _, cg := range cgs { - r, err := konnect.GetConsumerGroupObject(ctx, client, cg.ID) - if err != nil { - return nil, err - } - consumerGroupObjects = append(consumerGroupObjects, r) - } - return consumerGroupObjects, nil -} - // GetAllConsumerGroups queries Kong for all the ConsumerGroups using client. func GetAllConsumerGroups(ctx context.Context, client *kong.Client, tags []string, @@ -902,3 +874,15 @@ func excludeConsumersPlugins(plugins []*kong.Plugin) []*kong.Plugin { } return filtered } + +// excludeConsumerGroupsPlugins filter out consumer-groups plugins +func excludeConsumerGroupsPlugins(plugins []*kong.Plugin) []*kong.Plugin { + var filtered []*kong.Plugin + for _, p := range plugins { + if p.ConsumerGroup != nil && !utils.Empty(p.ConsumerGroup.ID) { + continue + } + filtered = append(filtered, p) + } + return filtered +} diff --git a/file/builder.go b/file/builder.go index a0286e215..7bbd52a2a 100644 --- a/file/builder.go +++ b/file/builder.go @@ -12,6 +12,8 @@ import ( "github.com/kong/go-kong/kong" ) +const ratelimitingAdvancedPluginName = "rate-limiting-advanced" + type stateBuilder struct { targetContent *Content rawState *utils.KongRawState @@ -35,6 +37,8 @@ type stateBuilder struct { checkRoutePaths bool + isConsumerGroupScopedPluginSupported bool + err error } @@ -69,6 +73,10 @@ func (b *stateBuilder) build() (*utils.KongRawState, *utils.KonnectRawState, err b.checkRoutePaths = true } + if utils.Kong340Version.LTE(b.kongVersion) || b.isKonnect { + b.isConsumerGroupScopedPluginSupported = true + } + // build b.certificates() if !b.skipCACerts { @@ -92,6 +100,46 @@ func (b *stateBuilder) build() (*utils.KongRawState, *utils.KonnectRawState, err return b.rawState, b.konnectRawState, nil } +func (b *stateBuilder) ingestConsumerGroupScopedPlugins(cg FConsumerGroupObject) error { + var plugins []FPlugin + for _, plugin := range cg.Plugins { + plugin.ConsumerGroup = utils.GetConsumerGroupReference(cg.ConsumerGroup) + plugins = append(plugins, FPlugin{ + Plugin: kong.Plugin{ + ID: plugin.ID, + Name: plugin.Name, + Config: plugin.Config, + ConsumerGroup: &kong.ConsumerGroup{ + ID: cg.ID, + }, + }, + }) + } + return b.ingestPlugins(plugins) +} + +func (b *stateBuilder) addConsumerGroupPlugins( + cg FConsumerGroupObject, cgo *kong.ConsumerGroupObject, +) error { + for _, plugin := range cg.Plugins { + if utils.Empty(plugin.ID) { + current, err := b.currentState.ConsumerGroupPlugins.Get( + *plugin.Name, *cg.ConsumerGroup.ID, + ) + if errors.Is(err, state.ErrNotFound) { + plugin.ID = uuid() + } else if err != nil { + return err + } else { + plugin.ID = kong.String(*current.ID) + } + } + b.defaulter.MustSet(plugin) + cgo.Plugins = append(cgo.Plugins, plugin) + } + return nil +} + func (b *stateBuilder) consumerGroups() { if b.err != nil { return @@ -116,22 +164,29 @@ func (b *stateBuilder) consumerGroups() { ConsumerGroup: &cg.ConsumerGroup, } - for _, plugin := range cg.Plugins { - if utils.Empty(plugin.ID) { - current, err := b.currentState.ConsumerGroupPlugins.Get( - *plugin.Name, *cg.ConsumerGroup.ID, - ) - if errors.Is(err, state.ErrNotFound) { - plugin.ID = uuid() - } else if err != nil { - b.err = err - return - } else { - plugin.ID = kong.String(*current.ID) - } + err := b.intermediate.ConsumerGroups.Add(state.ConsumerGroup{ConsumerGroup: cg.ConsumerGroup}) + if err != nil { + b.err = err + return + } + + // Plugins and Consumer Groups can be handled in two ways: + // 1. directly in the ConsumerGroup object + // 2. by scoping the plugin to the ConsumerGroup (Kong >= 3.4.0) + // + // The first method is deprecated and will be removed in the future, but + // we still need to support it for now. The isConsumerGroupScopedPluginSupported + // flag is used to determine which method to use based on the Kong version. + if b.isConsumerGroupScopedPluginSupported { + if err := b.ingestConsumerGroupScopedPlugins(cg); err != nil { + b.err = err + return + } + } else { + if err := b.addConsumerGroupPlugins(cg, &cgo); err != nil { + b.err = err + return } - b.defaulter.MustSet(plugin) - cgo.Plugins = append(cgo.Plugins, plugin) } b.rawState.ConsumerGroups = append(b.rawState.ConsumerGroups, &cgo) } @@ -234,10 +289,16 @@ func (b *stateBuilder) consumers() { for _, c := range b.targetContent.Consumers { c := c if utils.Empty(c.ID) { - consumer, err := b.currentState.Consumers.Get(*c.Username) - if errors.Is(err, state.ErrNotFound) { + var ( + consumer *state.Consumer + err error + ) + if c.Username != nil { + consumer, err = b.currentState.Consumers.GetByIDOrUsername(*c.Username) + } + if errors.Is(err, state.ErrNotFound) || consumer == nil { if c.CustomID != nil { - consumer, err = b.currentState.Consumers.Get(*c.CustomID) + consumer, err = b.currentState.Consumers.GetByCustomID(*c.CustomID) if err == nil { c.ID = kong.String(*consumer.ID) } @@ -844,7 +905,7 @@ func (b *stateBuilder) plugins() { for _, p := range b.targetContent.Plugins { p := p if p.Consumer != nil && !utils.Empty(p.Consumer.ID) { - c, err := b.intermediate.Consumers.Get(*p.Consumer.ID) + c, err := b.intermediate.Consumers.GetByIDOrUsername(*p.Consumer.ID) if errors.Is(err, state.ErrNotFound) { b.err = fmt.Errorf("consumer %v for plugin %v: %w", p.Consumer.FriendlyName(), *p.Name, err) @@ -882,6 +943,23 @@ func (b *stateBuilder) plugins() { } p.Route = utils.GetRouteReference(r.Route) } + if p.ConsumerGroup != nil && !utils.Empty(p.ConsumerGroup.ID) { + cg, err := b.intermediate.ConsumerGroups.Get(*p.ConsumerGroup.ID) + if errors.Is(err, state.ErrNotFound) { + b.err = fmt.Errorf("consumer-group %v for plugin %v: %w", + p.ConsumerGroup.FriendlyName(), *p.Name, err) + return + } else if err != nil { + b.err = err + return + } + p.ConsumerGroup = utils.GetConsumerGroupReference(cg.ConsumerGroup) + } + + if err := b.validatePlugin(p); err != nil { + b.err = err + return + } plugins = append(plugins, p) } if err := b.ingestPlugins(plugins); err != nil { @@ -890,6 +968,29 @@ func (b *stateBuilder) plugins() { } } +func (b *stateBuilder) validatePlugin(p FPlugin) error { + if b.isConsumerGroupScopedPluginSupported && *p.Name == ratelimitingAdvancedPluginName { + // check if deprecated consumer-groups configuration is present in the config + var consumerGroupsFound bool + if groups, ok := p.Config["consumer_groups"]; ok { + // if groups is an array of length > 0, then consumer_groups is set + if groupsArray, ok := groups.([]interface{}); ok && len(groupsArray) > 0 { + consumerGroupsFound = true + } + } + var enforceConsumerGroupsFound bool + if enforceConsumerGroups, ok := p.Config["enforce_consumer_groups"]; ok { + if enforceConsumerGroupsBool, ok := enforceConsumerGroups.(bool); ok && enforceConsumerGroupsBool { + enforceConsumerGroupsFound = true + } + } + if consumerGroupsFound || enforceConsumerGroupsFound { + return utils.ErrorConsumerGroupUpgrade + } + } + return nil +} + // strip_path schema default value is 'true', but it cannot be set when // protocols include 'grpc' and/or 'grpcs'. When users explicitly set // strip_path to 'true' with grpc/s protocols, deck returns a schema violation error. @@ -997,9 +1098,9 @@ func (b *stateBuilder) ingestPlugins(plugins []FPlugin) error { for _, p := range plugins { p := p if utils.Empty(p.ID) { - cID, rID, sID := pluginRelations(&p.Plugin) + cID, rID, sID, cgID := pluginRelations(&p.Plugin) plugin, err := b.currentState.Plugins.GetByProp(*p.Name, - sID, rID, cID) + sID, rID, cID, cgID) if errors.Is(err, state.ErrNotFound) { p.ID = uuid() } else if err != nil { @@ -1044,7 +1145,7 @@ func (b *stateBuilder) fillPluginConfig(plugin *FPlugin) error { return nil } -func pluginRelations(plugin *kong.Plugin) (cID, rID, sID string) { +func pluginRelations(plugin *kong.Plugin) (cID, rID, sID, cgID string) { if plugin.Consumer != nil && !utils.Empty(plugin.Consumer.ID) { cID = *plugin.Consumer.ID } @@ -1054,6 +1155,9 @@ func pluginRelations(plugin *kong.Plugin) (cID, rID, sID string) { if plugin.Service != nil && !utils.Empty(plugin.Service.ID) { sID = *plugin.Service.ID } + if plugin.ConsumerGroup != nil && !utils.Empty(plugin.ConsumerGroup.ID) { + cgID = *plugin.ConsumerGroup.ID + } return } diff --git a/file/builder_test.go b/file/builder_test.go index 0d3557786..1b79c2886 100644 --- a/file/builder_test.go +++ b/file/builder_test.go @@ -293,6 +293,9 @@ func existingPluginState() *state.KongState { Route: &kong.Route{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("69ed4618-a653-4b54-8bb6-dc33bd6fe048"), + }, }, }) return s @@ -751,6 +754,9 @@ func Test_stateBuilder_ingestPlugins(t *testing.T) { Route: &kong.Route{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("69ed4618-a653-4b54-8bb6-dc33bd6fe048"), + }, }, }, }, @@ -780,6 +786,9 @@ func Test_stateBuilder_ingestPlugins(t *testing.T) { Route: &kong.Route{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("69ed4618-a653-4b54-8bb6-dc33bd6fe048"), + }, Config: kong.Configuration{}, }, }, @@ -805,11 +814,12 @@ func Test_pluginRelations(t *testing.T) { plugin *kong.Plugin } tests := []struct { - name string - args args - wantCID string - wantRID string - wantSID string + name string + args args + wantCID string + wantRID string + wantSID string + wantCGID string }{ { args: args{ @@ -817,9 +827,10 @@ func Test_pluginRelations(t *testing.T) { Name: kong.String("foo"), }, }, - wantCID: "", - wantRID: "", - wantSID: "", + wantCID: "", + wantRID: "", + wantSID: "", + wantCGID: "", }, { args: args{ @@ -834,16 +845,20 @@ func Test_pluginRelations(t *testing.T) { Service: &kong.Service{ ID: kong.String("sID"), }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("cgID"), + }, }, }, - wantCID: "cID", - wantRID: "rID", - wantSID: "sID", + wantCID: "cID", + wantRID: "rID", + wantSID: "sID", + wantCGID: "cgID", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotCID, gotRID, gotSID := pluginRelations(tt.args.plugin) + gotCID, gotRID, gotSID, gotCGID := pluginRelations(tt.args.plugin) if gotCID != tt.wantCID { t.Errorf("pluginRelations() gotCID = %v, want %v", gotCID, tt.wantCID) } @@ -853,6 +868,9 @@ func Test_pluginRelations(t *testing.T) { if gotSID != tt.wantSID { t.Errorf("pluginRelations() gotSID = %v, want %v", gotSID, tt.wantSID) } + if gotCGID != tt.wantCGID { + t.Errorf("pluginRelations() gotCGID = %v, want %v", gotCGID, tt.wantCGID) + } }) } } diff --git a/file/codegen/main.go b/file/codegen/main.go index be480d51f..b9d5cacf0 100644 --- a/file/codegen/main.go +++ b/file/codegen/main.go @@ -22,12 +22,15 @@ var ( }, } - anyOfUsernameOrID = []*jsonschema.Type{ + // consumers + anyOfUsernameOrCustomID = []*jsonschema.Type{ { - Required: []string{"username"}, + Description: "at least one of custom_id or username must be set", + Required: []string{"username"}, }, { - Required: []string{"id"}, + Description: "at least one of custom_id or username must be set", + Required: []string{"custom_id"}, }, } ) @@ -52,7 +55,7 @@ func main() { schema.Definitions["FRoute"].AnyOf = anyOfNameOrID - schema.Definitions["FConsumer"].AnyOf = anyOfUsernameOrID + schema.Definitions["FConsumer"].AnyOf = anyOfUsernameOrCustomID schema.Definitions["FUpstream"].Required = []string{"name"} @@ -97,6 +100,7 @@ func main() { schema.Definitions["FPlugin"].Properties["consumer"] = stringType schema.Definitions["FPlugin"].Properties["service"] = stringType schema.Definitions["FPlugin"].Properties["route"] = stringType + schema.Definitions["FPlugin"].Properties["consumer_group"] = stringType schema.Definitions["FService"].Properties["client_certificate"] = stringType diff --git a/file/kong2kic.go b/file/kong2kic.go new file mode 100644 index 000000000..e4ff18322 --- /dev/null +++ b/file/kong2kic.go @@ -0,0 +1,1158 @@ +package file + +import ( + "encoding/json" + "log" + "strconv" + "strings" + + kicv1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1" + kicv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1" + k8scorev1 "k8s.io/api/core/v1" + k8snetv1 "k8s.io/api/networking/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// Builder + Director design pattern to create kubernetes manifests based on: +// 1 - Kong custom resource definitions +// 2 - Kong annotations +// 3 - Kubernetes Gateway spec +type IBuilder interface { + buildServices(*Content) + buildRoutes(*Content) + buildGlobalPlugins(*Content) + buildConsumers(*Content) + buildConsumerGroups(*Content) + getContent() *KICContent +} + +const ( + CUSTOM_RESOURCE = "CUSTOM_RESOURCE" + ANNOTATIONS = "ANNOTATIONS" + GATEWAY = "GATEWAY" +) + +func getBuilder(builderType string) IBuilder { + if builderType == CUSTOM_RESOURCE { + return newCustomResourceBuilder() + } + + if builderType == ANNOTATIONS { + return newAnnotationsBuilder() + } + + if builderType == GATEWAY { + // TODO: implement gateway builder + } + return nil +} + +type CustomResourceBuilder struct { + kicContent *KICContent +} + +func newCustomResourceBuilder() *CustomResourceBuilder { + return &CustomResourceBuilder{ + kicContent: &KICContent{}, + } +} + +func (b *CustomResourceBuilder) buildServices(content *Content) { + err := populateKICServicesWithCustomResources(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *CustomResourceBuilder) buildRoutes(content *Content) { + err := populateKICIngressesWithCustomResources(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *CustomResourceBuilder) buildGlobalPlugins(content *Content) { + err := populateKICKongClusterPlugins(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *CustomResourceBuilder) buildConsumers(content *Content) { + err := populateKICConsumers(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *CustomResourceBuilder) buildConsumerGroups(content *Content) { + err := populateKICConsumerGroups(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *CustomResourceBuilder) getContent() *KICContent { + return b.kicContent +} + +type AnnotationsBuilder struct { + kicContent *KICContent +} + +func newAnnotationsBuilder() *AnnotationsBuilder { + return &AnnotationsBuilder{ + kicContent: &KICContent{}, + } +} + +func (b *AnnotationsBuilder) buildServices(content *Content) { + err := populateKICServicesWithAnnotations(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *AnnotationsBuilder) buildRoutes(content *Content) { + err := populateKICIngressesWithAnnotations(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *AnnotationsBuilder) buildGlobalPlugins(content *Content) { + err := populateKICKongClusterPlugins(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *AnnotationsBuilder) buildConsumers(content *Content) { + err := populateKICConsumers(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *AnnotationsBuilder) buildConsumerGroups(content *Content) { + err := populateKICConsumerGroups(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *AnnotationsBuilder) getContent() *KICContent { + return b.kicContent +} + +type Director struct { + builder IBuilder +} + +func newDirector(builder IBuilder) *Director { + return &Director{ + builder: builder, + } +} + +func (d *Director) buildManifests(content *Content) *KICContent { + d.builder.buildServices(content) + d.builder.buildRoutes(content) + d.builder.buildGlobalPlugins(content) + d.builder.buildConsumers(content) + d.builder.buildConsumerGroups(content) + return d.builder.getContent() +} + +//////////////////// +/// End of Builder + Director +//////////////////// + +func MarshalKongToKICYaml(content *Content, builderType string) ([]byte, error) { + kicContent := convertKongToKIC(content, builderType) + return kicContent.marshalKICContentToYaml() +} + +func MarshalKongToKICJson(content *Content, builderType string) ([]byte, error) { + kicContent := convertKongToKIC(content, builderType) + return kicContent.marshalKICContentToJson() + +} + +func convertKongToKIC(content *Content, builderType string) *KICContent { + builder := getBuilder(builderType) + director := newDirector(builder) + return director.buildManifests(content) +} + +///// +// Functions valid for both custom resources and annotations based manifests +///// + +func populateKICKongClusterPlugins(content *Content, file *KICContent) error { + + // Global Plugins map to KongClusterPlugins + // iterate content.Plugins and copy them into kicv1.KongPlugin manifests + // add the kicv1.KongPlugin to the KICContent.KongClusterPlugins slice + for _, plugin := range content.Plugins { + var kongPlugin kicv1.KongClusterPlugin + kongPlugin.APIVersion = "configuration.konghq.com/v1" + kongPlugin.Kind = "KongClusterPlugin" + if plugin.InstanceName != nil { + kongPlugin.ObjectMeta.Name = *plugin.InstanceName + } + kongPlugin.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + if plugin.Name != nil { + kongPlugin.PluginName = *plugin.Name + } + + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + var configJSON apiextensionsv1.JSON + var err error + configJSON.Raw, err = json.Marshal(plugin.Config) + if err != nil { + return err + } + kongPlugin.Config = configJSON + file.KongClusterPlugins = append(file.KongClusterPlugins, kongPlugin) + } + return nil +} + +func populateKICConsumers(content *Content, file *KICContent) error { + // Iterate Kong Consumers and copy them into KongConsumer + for _, consumer := range content.Consumers { + var kongConsumer kicv1.KongConsumer + kongConsumer.APIVersion = "configuration.konghq.com/v1" + kongConsumer.Kind = "KongConsumer" + kongConsumer.ObjectMeta.Name = *consumer.Username + kongConsumer.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + kongConsumer.Username = *consumer.Username + if consumer.CustomID != nil { + kongConsumer.CustomID = *consumer.CustomID + } + + populateKICKeyAuthSecrets(&consumer, &kongConsumer, file) + populateKICHMACSecrets(&consumer, &kongConsumer, file) + populateKICJWTAuthSecrets(&consumer, &kongConsumer, file) + populateKICBasicAuthSecrets(&consumer, &kongConsumer, file) + populateKICOAuth2CredSecrets(&consumer, &kongConsumer, file) + populateKICACLGroupSecrets(&consumer, &kongConsumer, file) + populateKICMTLSAuthSecrets(&consumer, &kongConsumer, file) + + // for each consumer.plugin, create a KongPlugin and a plugin annotation in the kongConsumer + // to link the plugin + for _, plugin := range consumer.Plugins { + var kongPlugin kicv1.KongPlugin + kongPlugin.APIVersion = "configuration.konghq.com/v1" + kongPlugin.Kind = "KongPlugin" + if plugin.InstanceName != nil { + kongPlugin.ObjectMeta.Name = *plugin.InstanceName + } + kongPlugin.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + if plugin.Name != nil { + kongPlugin.PluginName = *plugin.Name + } + + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + var configJSON apiextensionsv1.JSON + var err error + configJSON.Raw, err = json.Marshal(plugin.Config) + if err != nil { + return err + } + kongPlugin.Config = configJSON + file.KongPlugins = append(file.KongPlugins, kongPlugin) + + if kongConsumer.ObjectMeta.Annotations["konghq.com/plugins"] == "" { + kongConsumer.ObjectMeta.Annotations["konghq.com/plugins"] = kongPlugin.PluginName + } else { + kongConsumer.ObjectMeta.Annotations["konghq.com/plugins"] = kongConsumer.ObjectMeta.Annotations["konghq.com/plugins"] + "," + kongPlugin.PluginName + } + } + + file.KongConsumers = append(file.KongConsumers, kongConsumer) + } + + return nil +} + +func populateKICMTLSAuthSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.MTLSAuths and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, mtlsAuth := range consumer.MTLSAuths { + var secret k8scorev1.Secret + var secretName = "mtls-auth-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.Type = "Opaque" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "mtls-auth" + + if mtlsAuth.SubjectName != nil { + secret.StringData["subject_name"] = *mtlsAuth.SubjectName + } + + if mtlsAuth.ID != nil { + secret.StringData["id"] = *mtlsAuth.ID + } + + if mtlsAuth.CACertificate != nil && mtlsAuth.CACertificate.Cert != nil { + secret.StringData["ca_certificate"] = *mtlsAuth.CACertificate.Cert + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICACLGroupSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.ACLGroups and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, aclGroup := range consumer.ACLGroups { + var secret k8scorev1.Secret + var secretName = "acl-group-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + + secret.StringData["kongCredType"] = "acl" + if aclGroup.Group != nil { + secret.StringData["group"] = *aclGroup.Group + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICOAuth2CredSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.OAuth2Creds and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, oauth2Cred := range consumer.Oauth2Creds { + var secret k8scorev1.Secret + var secretName = "oauth2cred-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "oauth2" + + if oauth2Cred.Name != nil { + secret.StringData["name"] = *oauth2Cred.Name + } + + if oauth2Cred.ClientID != nil { + secret.StringData["client_id"] = *oauth2Cred.ClientID + } + + if oauth2Cred.ClientSecret != nil { + secret.StringData["client_secret"] = *oauth2Cred.ClientSecret + } + + if oauth2Cred.ClientType != nil { + secret.StringData["client_type"] = *oauth2Cred.ClientType + } + + if oauth2Cred.HashSecret != nil { + secret.StringData["hash_secret"] = strconv.FormatBool(*oauth2Cred.HashSecret) + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICBasicAuthSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.BasicAuths and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, basicAuth := range consumer.BasicAuths { + var secret k8scorev1.Secret + var secretName = "basic-auth-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "basic-auth" + + if basicAuth.Username != nil { + secret.StringData["username"] = *basicAuth.Username + } + if basicAuth.Password != nil { + secret.StringData["password"] = *basicAuth.Password + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICJWTAuthSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.JWTAuths and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, jwtAuth := range consumer.JWTAuths { + var secret k8scorev1.Secret + var secretName = "jwt-auth-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "jwt" + + // only do the following assignments if not null + if jwtAuth.Key != nil { + secret.StringData["key"] = *jwtAuth.Key + } + + if jwtAuth.Algorithm != nil { + secret.StringData["algorithm"] = *jwtAuth.Algorithm + } + + if jwtAuth.RSAPublicKey != nil { + secret.StringData["rsa_public_key"] = *jwtAuth.RSAPublicKey + } + + if jwtAuth.Secret != nil { + secret.StringData["secret"] = *jwtAuth.Secret + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICHMACSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.HMACAuths and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, hmacAuth := range consumer.HMACAuths { + var secret k8scorev1.Secret + var secretName = "hmac-auth-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "hmac-auth" + + if hmacAuth.Username != nil { + secret.StringData["username"] = *hmacAuth.Username + } + + if hmacAuth.Secret != nil { + secret.StringData["secret"] = *hmacAuth.Secret + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICKeyAuthSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.KeyAuths and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + // add the secret name to the kongConsumer.credentials + for _, keyAuth := range consumer.KeyAuths { + var secret k8scorev1.Secret + var secretName = "key-auth-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "key-auth" + + if keyAuth.Key != nil { + secret.StringData["key"] = *keyAuth.Key + } + + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + + } +} + +func populateKICUpstream(content *Content, service *FService, k8sservice *k8scorev1.Service, kicContent *KICContent) { + + // add Kong specific configuration to the k8s service via a KongIngress resource + + if content.Upstreams != nil { + var kongIngress kicv1.KongIngress + kongIngress.APIVersion = "configuration.konghq.com/v1" + kongIngress.Kind = "KongIngress" + kongIngress.ObjectMeta.Name = *service.Name + kongIngress.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + + // add an annotation to the k8sservice to link this kongIngress to it + k8sservice.ObjectMeta.Annotations["konghq.com/override"] = kongIngress.ObjectMeta.Name + + // Find the upstream (if any) whose name matches the service host and copy the upstream + // into a kicv1.KongIngress resource. Append the kicv1.KongIngress to kicContent.KongIngresses. + for _, upstream := range content.Upstreams { + if upstream.Name != nil && strings.EqualFold(*upstream.Name, *service.Host) { + kongIngress.Upstream = &kicv1.KongIngressUpstream{ + HostHeader: upstream.HostHeader, + Algorithm: upstream.Algorithm, + Slots: upstream.Slots, + Healthchecks: upstream.Healthchecks, + HashOn: upstream.HashOn, + HashFallback: upstream.HashFallback, + HashOnHeader: upstream.HashOnHeader, + HashFallbackHeader: upstream.HashFallbackHeader, + HashOnCookie: upstream.HashOnCookie, + HashOnCookiePath: upstream.HashOnCookiePath, + HashOnQueryArg: upstream.HashOnQueryArg, + HashFallbackQueryArg: upstream.HashFallbackQueryArg, + HashOnURICapture: upstream.HashOnURICapture, + HashFallbackURICapture: upstream.HashFallbackURICapture, + } + } + } + kicContent.KongIngresses = append(kicContent.KongIngresses, kongIngress) + } +} + +func addPluginsToService(service FService, k8sService k8scorev1.Service, kicContent *KICContent) error { + for _, plugin := range service.Plugins { + var kongPlugin kicv1.KongPlugin + kongPlugin.APIVersion = "configuration.konghq.com/v1" + kongPlugin.Kind = "KongPlugin" + if plugin.Name != nil { + kongPlugin.ObjectMeta.Name = *plugin.Name + } + kongPlugin.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + kongPlugin.PluginName = *plugin.Name + + var configJSON apiextensionsv1.JSON + var err error + configJSON.Raw, err = json.Marshal(plugin.Config) + if err != nil { + return err + } + kongPlugin.Config = configJSON + + if k8sService.ObjectMeta.Annotations["konghq.com/plugins"] == "" { + k8sService.ObjectMeta.Annotations["konghq.com/plugins"] = kongPlugin.PluginName + } else { + k8sService.ObjectMeta.Annotations["konghq.com/plugins"] = k8sService.ObjectMeta.Annotations["konghq.com/plugins"] + "," + kongPlugin.PluginName + } + + kicContent.KongPlugins = append(kicContent.KongPlugins, kongPlugin) + } + return nil +} + +func addPluginsToRoute(route *FRoute, routeIngresses []k8snetv1.Ingress, kicContent *KICContent) error { + for _, plugin := range route.Plugins { + var kongPlugin kicv1.KongPlugin + kongPlugin.APIVersion = "configuration.konghq.com/v1" + kongPlugin.Kind = "KongPlugin" + if plugin.Name != nil { + kongPlugin.ObjectMeta.Name = *plugin.Name + } + kongPlugin.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + kongPlugin.PluginName = *plugin.Name + + var configJSON apiextensionsv1.JSON + var err error + configJSON.Raw, err = json.Marshal(plugin.Config) + if err != nil { + return err + } + kongPlugin.Config = configJSON + + for _, k8sIngress := range routeIngresses { + if k8sIngress.ObjectMeta.Annotations["konghq.com/plugins"] == "" { + k8sIngress.ObjectMeta.Annotations["konghq.com/plugins"] = kongPlugin.PluginName + } else { + k8sIngress.ObjectMeta.Annotations["konghq.com/plugins"] = k8sIngress.ObjectMeta.Annotations["konghq.com/plugins"] + "," + kongPlugin.PluginName + } + } + + kicContent.KongPlugins = append(kicContent.KongPlugins, kongPlugin) + } + return nil +} + +func fillIngressHostAndPortSection(host *string, service FService, k8sIngress *k8snetv1.Ingress, path *string, pathTypeImplSpecific k8snetv1.PathType) { + if host != nil && service.Port != nil { + k8sIngress.Spec.Rules = append(k8sIngress.Spec.Rules, k8snetv1.IngressRule{ + Host: *host, + IngressRuleValue: k8snetv1.IngressRuleValue{ + HTTP: &k8snetv1.HTTPIngressRuleValue{ + Paths: []k8snetv1.HTTPIngressPath{ + { + Path: *path, + PathType: &pathTypeImplSpecific, + Backend: k8snetv1.IngressBackend{ + Service: &k8snetv1.IngressServiceBackend{ + Name: *service.Name, + Port: k8snetv1.ServiceBackendPort{ + Number: int32(*service.Port), + }, + }, + }, + }, + }, + }, + }, + }) + } else if host == nil && service.Port != nil { + k8sIngress.Spec.Rules = append(k8sIngress.Spec.Rules, k8snetv1.IngressRule{ + IngressRuleValue: k8snetv1.IngressRuleValue{ + HTTP: &k8snetv1.HTTPIngressRuleValue{ + Paths: []k8snetv1.HTTPIngressPath{ + { + Path: *path, + PathType: &pathTypeImplSpecific, + Backend: k8snetv1.IngressBackend{ + Service: &k8snetv1.IngressServiceBackend{ + Name: *service.Name, + Port: k8snetv1.ServiceBackendPort{ + Number: int32(*service.Port), + }, + }, + }, + }, + }, + }, + }, + }) + } else if host != nil && service.Port == nil { + k8sIngress.Spec.Rules = append(k8sIngress.Spec.Rules, k8snetv1.IngressRule{ + Host: *host, + IngressRuleValue: k8snetv1.IngressRuleValue{ + HTTP: &k8snetv1.HTTPIngressRuleValue{ + Paths: []k8snetv1.HTTPIngressPath{ + { + Path: *path, + PathType: &pathTypeImplSpecific, + Backend: k8snetv1.IngressBackend{ + Service: &k8snetv1.IngressServiceBackend{ + Name: *service.Name, + }, + }, + }, + }, + }, + }, + }) + } else { + + k8sIngress.Spec.Rules = append(k8sIngress.Spec.Rules, k8snetv1.IngressRule{ + IngressRuleValue: k8snetv1.IngressRuleValue{ + HTTP: &k8snetv1.HTTPIngressRuleValue{ + Paths: []k8snetv1.HTTPIngressPath{ + { + Path: *path, + PathType: &pathTypeImplSpecific, + Backend: k8snetv1.IngressBackend{ + Service: &k8snetv1.IngressServiceBackend{ + Name: *service.Name, + }, + }, + }, + }, + }, + }, + }) + } +} + +func populateKICConsumerGroups(content *Content, kicContent *KICContent) error { + // iterate over the consumer groups and create a KongConsumerGroup for each one + for _, consumerGroup := range content.ConsumerGroups { + var kongConsumerGroup kicv1beta1.KongConsumerGroup + kongConsumerGroup.APIVersion = "configuration.konghq.com/v1" + kongConsumerGroup.Kind = "KongConsumerGroup" + kongConsumerGroup.ObjectMeta.Name = *consumerGroup.Name + kongConsumerGroup.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + kongConsumerGroup.Name = *consumerGroup.Name + + // Iterate over the consumers in consumerGroup and + // find the KongConsumer with the same username in kicContent.KongConsumers + // and add it to the KongConsumerGroup + for _, consumer := range consumerGroup.Consumers { + for idx, _ := range kicContent.KongConsumers { + if kicContent.KongConsumers[idx].Username == *consumer.Username { + if kicContent.KongConsumers[idx].ConsumerGroups == nil { + kicContent.KongConsumers[idx].ConsumerGroups = make([]string, 0) + } + kicContent.KongConsumers[idx].ConsumerGroups = append(kicContent.KongConsumers[idx].ConsumerGroups, *consumerGroup.Name) + } + } + } + + // for each consumerGroup.plugin, create a KongPlugin and a plugin annotation in the kongConsumerGroup + // to link the plugin + for _, plugin := range consumerGroup.Plugins { + var kongPlugin kicv1.KongPlugin + kongPlugin.APIVersion = "configuration.konghq.com/v1" + kongPlugin.Kind = "KongPlugin" + kongPlugin.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + if plugin.Name != nil { + kongPlugin.PluginName = *plugin.Name + } + + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + var configJSON apiextensionsv1.JSON + var err error + configJSON.Raw, err = json.Marshal(plugin.Config) + if err != nil { + return err + } + kongPlugin.Config = configJSON + kicContent.KongPlugins = append(kicContent.KongPlugins, kongPlugin) + + if kongConsumerGroup.ObjectMeta.Annotations["konghq.com/plugins"] == "" { + kongConsumerGroup.ObjectMeta.Annotations["konghq.com/plugins"] = kongPlugin.PluginName + } else { + kongConsumerGroup.ObjectMeta.Annotations["konghq.com/plugins"] = kongConsumerGroup.ObjectMeta.Annotations["konghq.com/plugins"] + "," + kongPlugin.PluginName + } + } + + kicContent.KongConsumerGroups = append(kicContent.KongConsumerGroups, kongConsumerGroup) + } + + return nil + +} + +///// +// Functions for CUSTOM RESOURCES based manifests +///// + +func populateKICServiceProxyAndUpstreamCustomResources(content *Content, service *FService, k8sservice *k8scorev1.Service, kicContent *KICContent) { + + // add Kong specific configuration to the k8s service via a KongIngress resource + + var kongIngress kicv1.KongIngress + kongIngress.APIVersion = "configuration.konghq.com/v1" + kongIngress.Kind = "KongIngress" + kongIngress.ObjectMeta.Name = *service.Name + kongIngress.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + + // add an annotation to the k8sservice to link this kongIngress to it + k8sservice.ObjectMeta.Annotations["konghq.com/override"] = kongIngress.ObjectMeta.Name + + // proxy attributes from the service to the kongIngress + kongIngress.Proxy = &kicv1.KongIngressService{ + Protocol: service.Protocol, + Path: service.Path, + Retries: service.Retries, + ConnectTimeout: service.ConnectTimeout, + WriteTimeout: service.WriteTimeout, + ReadTimeout: service.ReadTimeout, + } + + // Find the upstream (if any) whose name matches the service host and copy the upstream + // into a kicv1.KongIngress resource. Append the kicv1.KongIngress to kicContent.KongIngresses. + for _, upstream := range content.Upstreams { + if upstream.Name != nil && strings.EqualFold(*upstream.Name, *service.Host) { + kongIngress.Upstream = &kicv1.KongIngressUpstream{ + HostHeader: upstream.HostHeader, + Algorithm: upstream.Algorithm, + Slots: upstream.Slots, + Healthchecks: upstream.Healthchecks, + HashOn: upstream.HashOn, + HashFallback: upstream.HashFallback, + HashOnHeader: upstream.HashOnHeader, + HashFallbackHeader: upstream.HashFallbackHeader, + HashOnCookie: upstream.HashOnCookie, + HashOnCookiePath: upstream.HashOnCookiePath, + HashOnQueryArg: upstream.HashOnQueryArg, + HashFallbackQueryArg: upstream.HashFallbackQueryArg, + HashOnURICapture: upstream.HashOnURICapture, + HashFallbackURICapture: upstream.HashFallbackURICapture, + } + } + } + kicContent.KongIngresses = append(kicContent.KongIngresses, kongIngress) +} + +func populateKICServicesWithCustomResources(content *Content, kicContent *KICContent) error { + + // Iterate Kong Services and create k8s Services, + // then create KongIngress resources for Kong Service + // specific configuration and Upstream data. + // Finally, create KongPlugin resources for each plugin + // associated with the service. + for _, service := range content.Services { + var k8sService k8scorev1.Service + var protocol k8scorev1.Protocol + + k8sService.TypeMeta.APIVersion = "v1" + k8sService.TypeMeta.Kind = "Service" + if service.Name != nil { + k8sService.ObjectMeta.Name = *service.Name + } else { + log.Println("Service without a name is not recommended") + } + k8sService.ObjectMeta.Annotations = make(map[string]string) + + // default TCP unless service.Protocol is equal to k8scorev1.ProtocolUDP + if service.Protocol != nil && k8scorev1.Protocol(strings.ToUpper(*service.Protocol)) == k8scorev1.ProtocolUDP { + protocol = k8scorev1.ProtocolUDP + } else { + protocol = k8scorev1.ProtocolTCP + } + + if service.Port != nil { + sPort := k8scorev1.ServicePort{ + Protocol: protocol, + Port: int32(*service.Port), + TargetPort: intstr.IntOrString{IntVal: int32(*service.Port)}, + } + k8sService.Spec.Ports = append(k8sService.Spec.Ports, sPort) + } + + if service.Name != nil { + k8sService.Spec.Selector = map[string]string{"app": *service.Name} + } else { + log.Println("Service without a name is not recommended") + } + + populateKICServiceProxyAndUpstreamCustomResources(content, &service, &k8sService, kicContent) + + // iterate over the plugins for this service, create a KongPlugin for each one and add an annotation to the service + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + // create a plugins annotation in the k8sservice to link the plugin to it + error := addPluginsToService(service, k8sService, kicContent) + if error != nil { + return error + } + kicContent.Services = append(kicContent.Services, k8sService) + + } + return nil +} + +func populateKICIngressesWithCustomResources(content *Content, kicContent *KICContent) error { + + // Transform routes into k8s Ingress and KongIngress resources + // Assume each pair host/path will get its own ingress manifest + for _, service := range content.Services { + for _, route := range service.Routes { + // save all ingresses we create for this route so we can then + // assign them the plugins defined for the route + var routeIngresses []k8snetv1.Ingress + + // if there are no hosts just use the paths + if len(route.Hosts) == 0 { + routeIngresses = KongRoutePathToIngressPathCustomResources(route, nil, routeIngresses, kicContent, service) + } else { + // iterate over the hosts and paths and create an ingress for each + + for _, host := range route.Hosts { + // create a KongIngress resource and copy route data into it + // add annotation to the ingress to link it to the kongIngress + routeIngresses = KongRoutePathToIngressPathCustomResources(route, host, routeIngresses, kicContent, service) + + } + } + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + // create a plugins annotation in the routeIngresses to link them to this plugin. + // separate plugins with commas + error := addPluginsToRoute(route, routeIngresses, kicContent) + if error != nil { + return error + } + } + } + return nil + +} + +func KongRoutePathToIngressPathCustomResources(route *FRoute, host *string, routeIngresses []k8snetv1.Ingress, kicContent *KICContent, service FService) []k8snetv1.Ingress { + for _, path := range route.Paths { + var k8sIngress k8snetv1.Ingress + var pathTypeImplSpecific k8snetv1.PathType = k8snetv1.PathTypeImplementationSpecific + k8sIngress.TypeMeta.APIVersion = "networking.k8s.io/v1" + k8sIngress.TypeMeta.Kind = "Ingress" + k8sIngress.ObjectMeta.Name = *route.Name + ingressClassName := "kong" + k8sIngress.Spec.IngressClassName = &ingressClassName + + // Host and/or Service.Port can be nil. There are 4 possible combinations. + // host == nil && service.Port == nil + fillIngressHostAndPortSection(host, service, &k8sIngress, path, pathTypeImplSpecific) + + // Create a KongIngress resource and copy Kong specific route data into it + var kongIngress kicv1.KongIngress + kongIngress.APIVersion = "configuration.konghq.com/v1" + kongIngress.Kind = "KongIngress" + kongIngress.ObjectMeta.Name = *route.Name + kongIngress.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + + var kongProtocols []*kicv1.KongProtocol + for _, protocol := range route.Protocols { + p := kicv1.KongProtocol(*protocol) + kongProtocols = append(kongProtocols, &p) + } + + kongIngress.Route = &kicv1.KongIngressRoute{ + Methods: route.Methods, + Protocols: kongProtocols, + StripPath: route.StripPath, + PreserveHost: route.PreserveHost, + RegexPriority: route.RegexPriority, + HTTPSRedirectStatusCode: route.HTTPSRedirectStatusCode, + Headers: route.Headers, + PathHandling: route.PathHandling, + SNIs: route.SNIs, + RequestBuffering: route.RequestBuffering, + ResponseBuffering: route.ResponseBuffering, + } + + // add an annotation to the k8sIngress to link it to the kongIngress + k8sIngress.ObjectMeta.Annotations = map[string]string{"konghq.com/override": kongIngress.ObjectMeta.Name} + + routeIngresses = append(routeIngresses, k8sIngress) + + kicContent.Ingresses = append(kicContent.Ingresses, k8sIngress) + kicContent.KongIngresses = append(kicContent.KongIngresses, kongIngress) + } + return routeIngresses +} + +///// +// Functions for ANNOTATION based manifests +///// + +func populateKICServicesWithAnnotations(content *Content, kicContent *KICContent) error { + + // Iterate Kong Services and create k8s Services, + // then create KongIngress resources for Kong Service Upstream data. + // Finally, create KongPlugin resources for each plugin + // associated with the service. + for _, service := range content.Services { + var k8sService k8scorev1.Service + var protocol k8scorev1.Protocol + + k8sService.TypeMeta.APIVersion = "v1" + k8sService.TypeMeta.Kind = "Service" + if service.Name != nil { + k8sService.ObjectMeta.Name = *service.Name + } else { + log.Println("Service without a name is not recommended") + } + k8sService.ObjectMeta.Annotations = make(map[string]string) + + // default TCP unless service.Protocol is equal to k8scorev1.ProtocolUDP + if service.Protocol != nil && k8scorev1.Protocol(strings.ToUpper(*service.Protocol)) == k8scorev1.ProtocolUDP { + protocol = k8scorev1.ProtocolUDP + } else { + protocol = k8scorev1.ProtocolTCP + } + + if service.Port != nil { + sPort := k8scorev1.ServicePort{ + Protocol: protocol, + Port: int32(*service.Port), + TargetPort: intstr.IntOrString{IntVal: int32(*service.Port)}, + } + k8sService.Spec.Ports = append(k8sService.Spec.Ports, sPort) + } + + if service.Name != nil { + k8sService.Spec.Selector = map[string]string{"app": *service.Name} + } else { + log.Println("Service without a name is not recommended") + } + + // add konghq.com/read-timeout annotation if service.ReadTimeout is not nil + if service.ReadTimeout != nil { + k8sService.ObjectMeta.Annotations["konghq.com/read-timeout"] = strconv.Itoa(*service.ReadTimeout) + } + + // add konghq.com/write-timeout annotation if service.WriteTimeout is not nil + if service.WriteTimeout != nil { + k8sService.ObjectMeta.Annotations["konghq.com/write-timeout"] = strconv.Itoa(*service.WriteTimeout) + } + + // add konghq.com/connect-timeout annotation if service.ConnectTimeout is not nil + if service.ConnectTimeout != nil { + k8sService.ObjectMeta.Annotations["konghq.com/connect-timeout"] = strconv.Itoa(*service.ConnectTimeout) + } + + // add konghq.com/protocol annotation if service.Protocol is not nil + if service.Protocol != nil { + k8sService.ObjectMeta.Annotations["konghq.com/protocol"] = *service.Protocol + } + + // add konghq.com/path annotation if service.Path is not nil + if service.Path != nil { + k8sService.ObjectMeta.Annotations["konghq.com/path"] = *service.Path + } + + // add konghq.com/retries annotation if service.Retries is not nil + if service.Retries != nil { + k8sService.ObjectMeta.Annotations["konghq.com/retries"] = strconv.Itoa(*service.Retries) + } + + populateKICUpstream(content, &service, &k8sService, kicContent) + + // iterate over the plugins for this service, create a KongPlugin for each one and add an annotation to the service + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + // create a plugins annotation in the k8sservice to link the plugin to it + error := addPluginsToService(service, k8sService, kicContent) + if error != nil { + return error + } + + kicContent.Services = append(kicContent.Services, k8sService) + + } + return nil +} + +func populateKICIngressesWithAnnotations(content *Content, kicContent *KICContent) error { + + // Transform routes into k8s Ingress and KongIngress resources + // Assume each pair host/path will get its own ingress manifest + for _, service := range content.Services { + for _, route := range service.Routes { + // save all ingresses we create for this route so we can then + // assign them the plugins defined for the route + var routeIngresses []k8snetv1.Ingress + + // if there are no hosts just use the paths + if len(route.Hosts) == 0 { + routeIngresses = KongRoutePathToIngressPathAnnotations(route, nil, routeIngresses, kicContent, service) + } else { + // iterate over the hosts and paths and create an ingress for each + + for _, host := range route.Hosts { + // create a KongIngress resource and copy route data into it + // add annotation to the ingress to link it to the kongIngress + routeIngresses = KongRoutePathToIngressPathAnnotations(route, host, routeIngresses, kicContent, service) + + } + } + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + // create a plugins annotation in the routeIngresses to link them to this plugin. + // separate plugins with commas + error := addPluginsToRoute(route, routeIngresses, kicContent) + if error != nil { + return error + } + } + } + return nil +} + +func KongRoutePathToIngressPathAnnotations(route *FRoute, host *string, routeIngresses []k8snetv1.Ingress, file *KICContent, service FService) []k8snetv1.Ingress { + for _, path := range route.Paths { + var k8sIngress k8snetv1.Ingress + var pathTypeImplSpecific k8snetv1.PathType = k8snetv1.PathTypeImplementationSpecific + k8sIngress.TypeMeta.APIVersion = "networking.k8s.io/v1" + k8sIngress.TypeMeta.Kind = "Ingress" + k8sIngress.ObjectMeta.Name = *route.Name + ingressClassName := "kong" + k8sIngress.Spec.IngressClassName = &ingressClassName + k8sIngress.ObjectMeta.Annotations = make(map[string]string) + + // Host and/or Service.Port can be nil. There are 4 possible combinations. + // host == nil && service.Port == nil + fillIngressHostAndPortSection(host, service, &k8sIngress, path, pathTypeImplSpecific) + + // add konghq.com/protocols annotation if route.Protocols is not nil + if route.Protocols != nil { + var protocols string + for _, protocol := range route.Protocols { + if protocols == "" { + protocols = *protocol + } else { + protocols = protocols + "," + *protocol + } + } + k8sIngress.ObjectMeta.Annotations["konghq.com/protocols"] = protocols + } + + // add konghq.com/strip-path annotation if route.StripPath is not nil + if route.StripPath != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/strip-path"] = strconv.FormatBool(*route.StripPath) + } + + // add konghq.com/preserve-host annotation if route.PreserveHost is not nil + if route.PreserveHost != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/preserve-host"] = strconv.FormatBool(*route.PreserveHost) + } + + // add konghq.com/regex-priority annotation if route.RegexPriority is not nil + if route.RegexPriority != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/regex-priority"] = strconv.Itoa(*route.RegexPriority) + } + + // add konghq.com/https-redirect-status-code annotation if route.HTTPSRedirectStatusCode is not nil + if route.HTTPSRedirectStatusCode != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/https-redirect-status-code"] = strconv.Itoa(*route.HTTPSRedirectStatusCode) + } + + // add konghq.com/headers.* annotation if route.Headers is not nil + if route.Headers != nil { + for key, value := range route.Headers { + k8sIngress.ObjectMeta.Annotations["konghq.com/headers."+key] = strings.Join(value, ",") + } + } + + // add konghq.com/path-handling annotation if route.PathHandling is not nil + if route.PathHandling != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/path-handling"] = *route.PathHandling + } + + // add konghq.com/snis annotation if route.SNIs is not nil + if route.SNIs != nil { + var snis string + for _, sni := range route.SNIs { + if snis == "" { + snis = *sni + } else { + snis = snis + "," + *sni + } + } + k8sIngress.ObjectMeta.Annotations["konghq.com/snis"] = snis + } + + // add konghq.com/request-buffering annotation if route.RequestBuffering is not nil + if route.RequestBuffering != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/request-buffering"] = strconv.FormatBool(*route.RequestBuffering) + } + + // add konghq.com/response-buffering annotation if route.ResponseBuffering is not nil + if route.ResponseBuffering != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/response-buffering"] = strconv.FormatBool(*route.ResponseBuffering) + } + + // add konghq.com/methods annotation if route.Methods is not nil + if route.Methods != nil { + var methods string + for _, method := range route.Methods { + if methods == "" { + methods = *method + } else { + methods = methods + "," + *method + } + } + k8sIngress.ObjectMeta.Annotations["konghq.com/methods"] = methods + } + + routeIngresses = append(routeIngresses, k8sIngress) + + file.Ingresses = append(file.Ingresses, k8sIngress) + } + return routeIngresses +} diff --git a/file/kong2kic_test.go b/file/kong2kic_test.go new file mode 100644 index 000000000..4acf34ec4 --- /dev/null +++ b/file/kong2kic_test.go @@ -0,0 +1,209 @@ +package file + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_convertKongGatewayToIngress(t *testing.T) { + type args struct { + inputFilename string + outputFilenameYamlCRD string + outputFilenameYamlAnnotation string + outputFilenameJsonCRD string + outputFilenameJsonAnnotation string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "convert one service", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/1-service/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/1-service/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/1-service/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/1-service/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/1-service/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service and one route", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/2-service-and-route/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/2-service-and-route/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/2-service-and-route/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/2-service-and-route/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/2-service-and-route/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream data", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/3-service-and-upstream/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/3-service-and-upstream/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/3-service-and-upstream/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream and route", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/4-service-route-upstream/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/4-service-route-upstream/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/4-service-route-upstream/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, acl auth plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, basic auth plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, jwt auth plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, key auth plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, mtls auth plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, multiple plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert consumer groups", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/11-consumer-group/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/11-consumer-group/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/11-consumer-group/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/11-consumer-group/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/11-consumer-group/output-expected.json", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inputContent, err := GetContentFromFiles([]string{tt.args.inputFilename}, false) + if err != nil { + assert.Fail(t, err.Error()) + } + + output, err := MarshalKongToKICYaml(inputContent, CUSTOM_RESOURCE) + if (err != nil) != tt.wantErr { + t.Errorf("KongToKIC() error = %v, wantErr %v", err, tt.wantErr) + } + + if err == nil { + + expected, err := os.ReadFile(tt.args.outputFilenameYamlCRD) + if err != nil { + assert.Fail(t, err.Error()) + } + assert.Equal(t, string(expected), string(output)) + } + + output, err = MarshalKongToKICYaml(inputContent, ANNOTATIONS) + if (err != nil) != tt.wantErr { + t.Errorf("KongToKIC() error = %v, wantErr %v", err, tt.wantErr) + } + + if err == nil { + + expected, err := os.ReadFile(tt.args.outputFilenameYamlAnnotation) + if err != nil { + assert.Fail(t, err.Error()) + } + assert.Equal(t, string(expected), string(output)) + } + + output, err = MarshalKongToKICJson(inputContent, CUSTOM_RESOURCE) + if (err != nil) != tt.wantErr { + t.Errorf("KongToKIC() error = %v, wantErr %v", err, tt.wantErr) + } + + if err == nil { + + expected, err := os.ReadFile(tt.args.outputFilenameJsonCRD) + if err != nil { + assert.Fail(t, err.Error()) + } + assert.Equal(t, string(expected), string(output)) + } + + output, err = MarshalKongToKICJson(inputContent, ANNOTATIONS) + if (err != nil) != tt.wantErr { + t.Errorf("KongToKIC() error = %v, wantErr %v", err, tt.wantErr) + } + + if err == nil { + + expected, err := os.ReadFile(tt.args.outputFilenameJsonAnnotation) + if err != nil { + assert.Fail(t, err.Error()) + } + assert.Equal(t, string(expected), string(output)) + } + }) + } +} diff --git a/file/kong_json_schema.json b/file/kong_json_schema.json index b287a13ff..76d09f68a 100644 --- a/file/kong_json_schema.json +++ b/file/kong_json_schema.json @@ -444,7 +444,6 @@ }, "groups": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/ConsumerGroup" }, "type": "array" @@ -509,12 +508,14 @@ { "required": [ "username" - ] + ], + "description": "at least one of custom_id or username must be set" }, { "required": [ - "id" - ] + "custom_id" + ], + "description": "at least one of custom_id or username must be set" } ] }, @@ -600,6 +601,9 @@ "consumer": { "type": "string" }, + "consumer_group": { + "type": "string" + }, "created_at": { "type": "integer" }, diff --git a/file/reader.go b/file/reader.go index 81ba985e7..1e53895ff 100644 --- a/file/reader.go +++ b/file/reader.go @@ -33,12 +33,12 @@ type RenderConfig struct { // // It will return an error if the file representation is invalid // or if there is any error during processing. -func GetContentFromFiles(filenames []string) (*Content, error) { +func GetContentFromFiles(filenames []string, mockEnvVars bool) (*Content, error) { if len(filenames) == 0 { return nil, ErrorFilenameEmpty } - return getContent(filenames) + return getContent(filenames, mockEnvVars) } // GetForKonnect processes the fileContent and renders a RawState and KonnectRawState diff --git a/file/reader_test.go b/file/reader_test.go index ebf5e7ea0..aae486134 100644 --- a/file/reader_test.go +++ b/file/reader_test.go @@ -68,7 +68,7 @@ func TestReadKongStateFromStdinFailsToParseText(t *testing.T) { os.Stdin = tmpfile - c, err := GetContentFromFiles(filenames) + c, err := GetContentFromFiles(filenames, false) assert.NotNil(err) assert.Nil(c) } @@ -97,7 +97,7 @@ func TestTransformNotFalse(t *testing.T) { os.Stdin = tmpfile - c, err := GetContentFromFiles(filenames) + c, err := GetContentFromFiles(filenames, false) if err != nil { panic(err) } @@ -139,7 +139,7 @@ func TestReadKongStateFromStdin(t *testing.T) { os.Stdin = tmpfile - c, err := GetContentFromFiles(filenames) + c, err := GetContentFromFiles(filenames, false) assert.NotNil(c) assert.Nil(err) @@ -155,7 +155,7 @@ func TestReadKongStateFromFile(t *testing.T) { assert := assert.New(t) assert.Equal("testdata/config.yaml", filenames[0]) - c, err := GetContentFromFiles(filenames) + c, err := GetContentFromFiles(filenames, false) assert.NotNil(c) assert.Nil(err) diff --git a/file/readfile.go b/file/readfile.go index 93054c8e3..165f7b6b1 100644 --- a/file/readfile.go +++ b/file/readfile.go @@ -19,45 +19,55 @@ import ( // getContent reads all the YAML and JSON files in the directory or the // file, depending on the type of each item in filenames, merges the content of // these files and renders a Content. -func getContent(filenames []string) (*Content, error) { - var allReaders []io.Reader - var workspaces []string +func getContent(filenames []string, mockEnvVars bool) (*Content, error) { + var workspaces, runtimeGroups []string + var res Content + var errs []error for _, fileOrDir := range filenames { readers, err := getReaders(fileOrDir) if err != nil { return nil, err } - allReaders = append(allReaders, readers...) - } - var res Content - for _, r := range allReaders { - content, err := readContent(r) - if err != nil { - return nil, fmt.Errorf("reading file: %w", err) - } - if content.Workspace != "" { - workspaces = append(workspaces, content.Workspace) - } - err = mergo.Merge(&res, content, mergo.WithAppendSlice) - if err != nil { - return nil, fmt.Errorf("merging file contents: %w", err) + + for filename, r := range readers { + content, err := readContent(r, mockEnvVars) + if err != nil { + errs = append(errs, fmt.Errorf("reading file %s: %w", filename, err)) + continue + } + if content.Workspace != "" { + workspaces = append(workspaces, content.Workspace) + } + if content.Konnect != nil && len(content.Konnect.RuntimeGroupName) > 0 { + runtimeGroups = append(runtimeGroups, content.Konnect.RuntimeGroupName) + } + err = mergo.Merge(&res, content, mergo.WithAppendSlice) + if err != nil { + return nil, fmt.Errorf("merging file contents: %w", err) + } } } + if len(errs) > 0 { + return nil, utils.ErrArray{Errors: errs} + } if err := validateWorkspaces(workspaces); err != nil { return nil, err } + if err := validateRuntimeGroups(runtimeGroups); err != nil { + return nil, err + } return &res, nil } -// getReaders returns back io.Readers representing all the YAML and JSON -// files in a directory. If fileOrDir is a single file, then it +// getReaders returns back a map of filename:io.Reader representing all the +// YAML and JSON files in a directory. If fileOrDir is a single file, then it // returns back the reader for the file. // If fileOrDir is equal to "-" string, then it returns back a io.Reader // for the os.Stdin file descriptor. -func getReaders(fileOrDir string) ([]io.Reader, error) { +func getReaders(fileOrDir string) (map[string]io.Reader, error) { // special case where `-` means stdin if fileOrDir == "-" { - return []io.Reader{os.Stdin}, nil + return map[string]io.Reader{"STDIN": os.Stdin}, nil } finfo, err := os.Stat(fileOrDir) @@ -75,13 +85,13 @@ func getReaders(fileOrDir string) ([]io.Reader, error) { files = append(files, fileOrDir) } - var res []io.Reader + res := make(map[string]io.Reader, len(files)) for _, file := range files { f, err := os.Open(file) if err != nil { return nil, fmt.Errorf("opening file: %w", err) } - res = append(res, bufio.NewReader(f)) + res[file] = bufio.NewReader(f) } return res, nil } @@ -95,13 +105,13 @@ func hasLeadingSpace(fileContent string) bool { // readContent reads all the byes until io.EOF and unmarshals the read // bytes into Content. -func readContent(reader io.Reader) (*Content, error) { +func readContent(reader io.Reader, mockEnvVars bool) (*Content, error) { var err error contentBytes, err := ioutil.ReadAll(reader) if err != nil { return nil, err } - renderedContent, err := renderTemplate(string(contentBytes)) + renderedContent, err := renderTemplate(string(contentBytes), mockEnvVars) if err != nil { return nil, fmt.Errorf("parsing file: %w", err) } @@ -146,31 +156,73 @@ func getPrefixedEnvVar(key string) (string, error) { return value, nil } +// getPrefixedEnvVarMocked is used when we mock the env variables while rendering a template. +// It will always return the name of the environment variable in this case. +func getPrefixedEnvVarMocked(key string) (string, error) { + const envVarPrefix = "DECK_" + if !strings.HasPrefix(key, envVarPrefix) { + return "", fmt.Errorf("environment variables in the state file must "+ + "be prefixed with 'DECK_', found: '%s'", key) + } + return key, nil +} + func toBool(key string) (bool, error) { return strconv.ParseBool(key) } +// toBoolMocked is used when we mock the env variables while rendering a template. +// It will always return false in this case. +func toBoolMocked(_ string) (bool, error) { + return false, nil +} + func toInt(key string) (int, error) { return strconv.Atoi(key) } +// toIntMocked is used when we mock the env variables while rendering a template. +// It will always return 42 in this case. +func toIntMocked(_ string) (int, error) { + return 42, nil +} + func toFloat(key string) (float64, error) { return strconv.ParseFloat(key, 64) } +// toFloatMocked is used when we mock the env variables while rendering a template. +// It will always return 42 in this case. +func toFloatMocked(_ string) (float64, error) { + return 42, nil +} + func indent(spaces int, v string) string { pad := strings.Repeat(" ", spaces) return strings.Replace(v, "\n", "\n"+pad, -1) } -func renderTemplate(content string) (string, error) { - t := template.New("state").Funcs(template.FuncMap{ - "env": getPrefixedEnvVar, - "toBool": toBool, - "toInt": toInt, - "toFloat": toFloat, - "indent": indent, - }).Delims("${{", "}}") +func renderTemplate(content string, mockEnvVars bool) (string, error) { + var templateFuncs template.FuncMap + if mockEnvVars { + templateFuncs = template.FuncMap{ + "env": getPrefixedEnvVarMocked, + "toBool": toBoolMocked, + "toInt": toIntMocked, + "toFloat": toFloatMocked, + "indent": indent, + } + } else { + templateFuncs = template.FuncMap{ + "env": getPrefixedEnvVar, + "toBool": toBool, + "toInt": toInt, + "toFloat": toFloat, + "indent": indent, + } + } + t := template.New("state").Funcs(templateFuncs).Delims("${{", "}}") + t, err := t.Parse(content) if err != nil { return "", err diff --git a/file/readfile_test.go b/file/readfile_test.go index df2a49285..573f0aa45 100644 --- a/file/readfile_test.go +++ b/file/readfile_test.go @@ -6,9 +6,10 @@ import ( "reflect" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" - "github.com/stretchr/testify/require" ) func Test_configFilesInDir(t *testing.T) { @@ -66,15 +67,17 @@ func Test_getReaders(t *testing.T) { tests := []struct { name string args args - want []io.Reader + want map[string]io.Reader // length of returned array wantLen int wantErr bool }{ { - name: "read from standard input", - args: args{"-"}, - want: []io.Reader{os.Stdin}, + name: "read from standard input", + args: args{"-"}, + want: map[string]io.Reader{ + "STDIN": os.Stdin, + }, wantLen: 1, wantErr: false, }, @@ -126,6 +129,29 @@ func Test_getReaders(t *testing.T) { } } +func sortSlices(x, y interface{}) bool { + var xName, yName string + switch xEntity := x.(type) { + case FService: + yEntity := y.(FService) + xName = *xEntity.Name + yName = *yEntity.Name + case FRoute: + yEntity := y.(FRoute) + xName = *xEntity.Name + yName = *yEntity.Name + case FConsumer: + yEntity := y.(FConsumer) + xName = *xEntity.Username + yName = *yEntity.Username + case FPlugin: + yEntity := y.(FPlugin) + xName = *xEntity.Name + yName = *yEntity.Name + } + return xName < yName +} + func Test_getContent(t *testing.T) { type args struct { filenames []string @@ -372,6 +398,12 @@ kong.log.set_serialize_value("span_id", parse_traceid(ngx.ctx.KONG_SPANS[1].span want: nil, wantErr: true, }, + { + name: "different runtime groups", + args: args{[]string{"testdata/differentruntimegroup"}}, + want: nil, + wantErr: true, + }, { name: "same workspaces", args: args{[]string{"testdata/sameworkspace"}}, @@ -551,12 +583,20 @@ kong.log.set_serialize_value("span_id", parse_traceid(ngx.ctx.KONG_SPANS[1].span for k, v := range tt.envVars { t.Setenv(k, v) } - got, err := getContent(tt.args.filenames) + got, err := getContent(tt.args.filenames, false) if (err != nil) != tt.wantErr { t.Errorf("getContent() error = %v, wantErr %v", err, tt.wantErr) return } - require.Equal(t, tt.want, got) + + opt := []cmp.Option{ + cmpopts.SortSlices(sortSlices), + cmpopts.SortSlices(func(a, b *string) bool { return *a < *b }), + cmpopts.EquateEmpty(), + } + if diff := cmp.Diff(got, tt.want, opt...); diff != "" { + t.Errorf(diff) + } }) } } diff --git a/file/testdata/differentruntimegroup/bar.yaml b/file/testdata/differentruntimegroup/bar.yaml new file mode 100644 index 000000000..a613369e2 --- /dev/null +++ b/file/testdata/differentruntimegroup/bar.yaml @@ -0,0 +1,6 @@ +_format_version: "3.0" +_konnect: + runtime_group_name: bar +services: +- name: svc2 + host: 2.example.com \ No newline at end of file diff --git a/file/testdata/differentruntimegroup/foo.yaml b/file/testdata/differentruntimegroup/foo.yaml new file mode 100644 index 000000000..8a91952b2 --- /dev/null +++ b/file/testdata/differentruntimegroup/foo.yaml @@ -0,0 +1,6 @@ +_format_version: "3.0" +_konnect: + runtime_group_name: foo +services: +- name: svc1 + host: 1.example.com \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/1-service/input.yaml b/file/testdata/kong2kic/annotations/json/1-service/input.yaml new file mode 100644 index 000000000..2632c2c53 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/1-service/input.yaml @@ -0,0 +1,16 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/1-service/output-expected.json b/file/testdata/kong2kic/annotations/json/1-service/output-expected.json new file mode 100644 index 000000000..69c6f5b8e --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/1-service/output-expected.json @@ -0,0 +1,31 @@ +{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/input.yaml b/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/input.yaml new file mode 100644 index 000000000..316ff1b35 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/input.yaml @@ -0,0 +1,194 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: aws-lambda + config: + aws_key: my_key + aws_secret: my_secret + function_name: my_function + aws_region: us-west-2 + - name: cors + config: + origins: + - example.com + methods: + - GET + - POST + headers: + - Authorization + exposed_headers: + - X-My-Header + max_age: 3600 + credentials: true + - name: file-log + config: + path: /var/log/kong/kong.log + reopen: true + - name: http-log + config: + http_endpoint: http://example.com/logs + method: POST + content_type: application/json + timeout: 10000 + keepalive: 60000 + retry_count: 10 + queue_size: 1000 + - name: ip-restriction + config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 + - name: rate-limiting-advanced + config: + limit: + - 5 + window_size: + - 30 + identifier: consumer + sync_rate: -1 + namespace: example_namespace + strategy: local + hide_client_headers: false + - name: request-termination + config: + status_code: 403 + message: Forbidden + - name: response-ratelimiting + config: + limits: + limit_name: + minute: 10 + policy: local + - name: tcp-log + config: + host: example.com + port: 1234 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/output-expected.json b/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/output-expected.json new file mode 100644 index 000000000..8bf257ff6 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/output-expected.json @@ -0,0 +1,371 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "aws-lambda", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "aws_key": "my_key", + "aws_region": "us-west-2", + "aws_secret": "my_secret", + "function_name": "my_function" + }, + "plugin": "aws-lambda", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "cors", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "credentials": true, + "exposed_headers": [ + "X-My-Header" + ], + "headers": [ + "Authorization" + ], + "max_age": 3600, + "methods": [ + "GET", + "POST" + ], + "origins": [ + "example.com" + ] + }, + "plugin": "cors", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "file-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "path": "/var/log/kong/kong.log", + "reopen": true + }, + "plugin": "file-log", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "http-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "content_type": "application/json", + "http_endpoint": "http://example.com/logs", + "keepalive": 60000, + "method": "POST", + "queue_size": 1000, + "retry_count": 10, + "timeout": 10000 + }, + "plugin": "http-log", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "ip-restriction", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "allow": [ + "192.168.0.1/24" + ], + "deny": [ + "192.168.0.2/32" + ] + }, + "plugin": "ip-restriction", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "rate-limiting-advanced", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_client_headers": false, + "identifier": "consumer", + "limit": [ + 5 + ], + "namespace": "example_namespace", + "strategy": "local", + "sync_rate": -1, + "window_size": [ + 30 + ] + }, + "plugin": "rate-limiting-advanced", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "request-termination", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "message": "Forbidden", + "status_code": 403 + }, + "plugin": "request-termination", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "response-ratelimiting", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "limits": { + "limit_name": { + "minute": 10 + } + }, + "policy": "local" + }, + "plugin": "response-ratelimiting", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "tcp-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "host": "example.com", + "port": 1234 + }, + "plugin": "tcp-log", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "aws-lambda,cors,file-log,http-log,ip-restriction,rate-limiting-advanced,request-termination,response-ratelimiting,tcp-log", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/11-consumer-group/input.yaml b/file/testdata/kong2kic/annotations/json/11-consumer-group/input.yaml new file mode 100644 index 000000000..b57971e33 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/11-consumer-group/input.yaml @@ -0,0 +1,9 @@ +consumer_groups: + - name: example-consumer-group + consumers: + - username: example-user +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/11-consumer-group/output-expected.json b/file/testdata/kong2kic/annotations/json/11-consumer-group/output-expected.json new file mode 100644 index 000000000..3e8cf4b1e --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/11-consumer-group/output-expected.json @@ -0,0 +1,28 @@ +{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "consumerGroups": [ + "example-consumer-group" + ], + "status": {} +}{ + "kind": "KongConsumerGroup", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-consumer-group", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/2-service-and-route/input.yaml b/file/testdata/kong2kic/annotations/json/2-service-and-route/input.yaml new file mode 100644 index 000000000..96ffcce6e --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/2-service-and-route/input.yaml @@ -0,0 +1,44 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/2-service-and-route/output-expected.json b/file/testdata/kong2kic/annotations/json/2-service-and-route/output-expected.json new file mode 100644 index 000000000..62667827e --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/2-service-and-route/output-expected.json @@ -0,0 +1,76 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/3-service-and-upstream/input.yaml b/file/testdata/kong2kic/annotations/json/3-service-and-upstream/input.yaml new file mode 100644 index 000000000..0ee8eeadd --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/3-service-and-upstream/input.yaml @@ -0,0 +1,97 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/annotations/json/3-service-and-upstream/output-expected.json b/file/testdata/kong2kic/annotations/json/3-service-and-upstream/output-expected.json new file mode 100644 index 000000000..f770a97cf --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/3-service-and-upstream/output-expected.json @@ -0,0 +1,131 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/4-service-route-upstream/input.yaml b/file/testdata/kong2kic/annotations/json/4-service-route-upstream/input.yaml new file mode 100644 index 000000000..9e4e5ec86 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/4-service-route-upstream/input.yaml @@ -0,0 +1,125 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/annotations/json/4-service-route-upstream/output-expected.json b/file/testdata/kong2kic/annotations/json/4-service-route-upstream/output-expected.json new file mode 100644 index 000000000..10f4d1792 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/4-service-route-upstream/output-expected.json @@ -0,0 +1,176 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/input.yaml b/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/input.yaml new file mode 100644 index 000000000..e9fb52034 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: acl + config: + allow: + - admin +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + acls: + - group: acl_group + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/output-expected.json b/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/output-expected.json new file mode 100644 index 000000000..3cb78115e --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/output-expected.json @@ -0,0 +1,224 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "acl", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "allow": [ + "admin" + ] + }, + "plugin": "acl", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "acl", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "acl-group-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "group": "acl_group", + "kongCredType": "acl" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "acl-group-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/input.yaml b/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/input.yaml new file mode 100644 index 000000000..163846d4b --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: basic-auth + config: + hide_credentials: false +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + basicauth_credentials: + - username: my_basic_user + password: my_basic_password + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/output-expected.json b/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/output-expected.json new file mode 100644 index 000000000..9842a161a --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/output-expected.json @@ -0,0 +1,223 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "basic-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_credentials": false + }, + "plugin": "basic-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "basic-auth", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "basic-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "kongCredType": "basic-auth", + "password": "my_basic_password", + "username": "my_basic_user" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "basic-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/input.yaml b/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/input.yaml new file mode 100644 index 000000000..363f14ea3 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/input.yaml @@ -0,0 +1,151 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: jwt + config: + uri_param_names: + - token + claims_to_verify: + - exp + - nbf + key_claim_name: kid + secret_is_base64: false + anonymous: null + run_on_preflight: true + maximum_expiration: 3600 + header_names: + - Authorization +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + jwt_secrets: + - key: my_jwt_secret + algorithm: HS256 + secret: my_secret_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/output-expected.json b/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/output-expected.json new file mode 100644 index 000000000..109116be9 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/output-expected.json @@ -0,0 +1,238 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "jwt", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "anonymous": null, + "claims_to_verify": [ + "exp", + "nbf" + ], + "header_names": [ + "Authorization" + ], + "key_claim_name": "kid", + "maximum_expiration": 3600, + "run_on_preflight": true, + "secret_is_base64": false, + "uri_param_names": [ + "token" + ] + }, + "plugin": "jwt", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "jwt", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "jwt-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "algorithm": "HS256", + "key": "my_jwt_secret", + "kongCredType": "jwt", + "secret": "my_secret_key" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "jwt-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/input.yaml b/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/input.yaml new file mode 100644 index 000000000..754bb520c --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/input.yaml @@ -0,0 +1,142 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: key-auth + config: + hide_credentials: false + key_names: + - apikey + key_in_body: false + run_on_preflight: true +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + keyauth_credentials: + - key: my_api_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/output-expected.json b/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/output-expected.json new file mode 100644 index 000000000..98a725690 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/output-expected.json @@ -0,0 +1,227 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "key-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_credentials": false, + "key_in_body": false, + "key_names": [ + "apikey" + ], + "run_on_preflight": true + }, + "plugin": "key-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "key-auth", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "key-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "key": "my_api_key", + "kongCredType": "key-auth" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "key-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/input.yaml b/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/input.yaml new file mode 100644 index 000000000..f12055648 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/input.yaml @@ -0,0 +1,140 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: mtls-auth + config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + skip_consumer_lookup: false + revocation_check_mode: SKIP +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + mtls_auth_credentials: + - id: cce8c384-721f-4f58-85dd-50834e3e733a + subject_name: example-user@example.com \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/output-expected.json b/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/output-expected.json new file mode 100644 index 000000000..de585a5b6 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/output-expected.json @@ -0,0 +1,228 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "mtls-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "ca_certificates": [ + "cce8c384-721f-4f58-85dd-50834e3e733a" + ], + "revocation_check_mode": "SKIP", + "skip_consumer_lookup": false + }, + "plugin": "mtls-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "mtls-auth", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "mtls-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "id": "cce8c384-721f-4f58-85dd-50834e3e733a", + "kongCredType": "mtls-auth", + "subject_name": "example-user@example.com" + }, + "type": "Opaque" +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "mtls-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/1-service/input.yaml b/file/testdata/kong2kic/annotations/yaml/1-service/input.yaml new file mode 100644 index 000000000..2632c2c53 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/1-service/input.yaml @@ -0,0 +1,16 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/1-service/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/1-service/output-expected.yaml new file mode 100644 index 000000000..9df248f36 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/1-service/output-expected.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/input.yaml b/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/input.yaml new file mode 100644 index 000000000..316ff1b35 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/input.yaml @@ -0,0 +1,194 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: aws-lambda + config: + aws_key: my_key + aws_secret: my_secret + function_name: my_function + aws_region: us-west-2 + - name: cors + config: + origins: + - example.com + methods: + - GET + - POST + headers: + - Authorization + exposed_headers: + - X-My-Header + max_age: 3600 + credentials: true + - name: file-log + config: + path: /var/log/kong/kong.log + reopen: true + - name: http-log + config: + http_endpoint: http://example.com/logs + method: POST + content_type: application/json + timeout: 10000 + keepalive: 60000 + retry_count: 10 + queue_size: 1000 + - name: ip-restriction + config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 + - name: rate-limiting-advanced + config: + limit: + - 5 + window_size: + - 30 + identifier: consumer + sync_rate: -1 + namespace: example_namespace + strategy: local + hide_client_headers: false + - name: request-termination + config: + status_code: 403 + message: Forbidden + - name: response-ratelimiting + config: + limits: + limit_name: + minute: 10 + policy: local + - name: tcp-log + config: + host: example.com + port: 1234 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/output-expected.yaml new file mode 100644 index 000000000..1d6ae5982 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/output-expected.yaml @@ -0,0 +1,292 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + aws_key: my_key + aws_region: us-west-2 + aws_secret: my_secret + function_name: my_function +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: aws-lambda +plugin: aws-lambda +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + credentials: true + exposed_headers: + - X-My-Header + headers: + - Authorization + max_age: 3600 + methods: + - GET + - POST + origins: + - example.com +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: cors +plugin: cors +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + path: /var/log/kong/kong.log + reopen: true +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: file-log +plugin: file-log +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + content_type: application/json + http_endpoint: http://example.com/logs + keepalive: 60000 + method: POST + queue_size: 1000 + retry_count: 10 + timeout: 10000 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: http-log +plugin: http-log +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: ip-restriction +plugin: ip-restriction +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_client_headers: false + identifier: consumer + limit: + - 5 + namespace: example_namespace + strategy: local + sync_rate: -1 + window_size: + - 30 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: rate-limiting-advanced +plugin: rate-limiting-advanced +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + message: Forbidden + status_code: 403 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: request-termination +plugin: request-termination +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + limits: + limit_name: + minute: 10 + policy: local +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: response-ratelimiting +plugin: response-ratelimiting +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + host: example.com + port: 1234 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: tcp-log +plugin: tcp-log +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: aws-lambda,cors,file-log,http-log,ip-restriction,rate-limiting-advanced,request-termination,response-ratelimiting,tcp-log + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: configuration.konghq.com/v1 +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/annotations/yaml/11-consumer-group/input.yaml b/file/testdata/kong2kic/annotations/yaml/11-consumer-group/input.yaml new file mode 100644 index 000000000..b57971e33 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/11-consumer-group/input.yaml @@ -0,0 +1,9 @@ +consumer_groups: + - name: example-consumer-group + consumers: + - username: example-user +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/11-consumer-group/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/11-consumer-group/output-expected.yaml new file mode 100644 index 000000000..19827ed76 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/11-consumer-group/output-expected.yaml @@ -0,0 +1,22 @@ +apiVersion: configuration.konghq.com/v1 +consumerGroups: +- example-consumer-group +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- +apiVersion: configuration.konghq.com/v1 +kind: KongConsumerGroup +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-consumer-group +status: {} +--- diff --git a/file/testdata/kong2kic/annotations/yaml/2-service-and-route/input.yaml b/file/testdata/kong2kic/annotations/yaml/2-service-and-route/input.yaml new file mode 100644 index 000000000..96ffcce6e --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/2-service-and-route/input.yaml @@ -0,0 +1,44 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/2-service-and-route/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/2-service-and-route/output-expected.yaml new file mode 100644 index 000000000..a69aee1d9 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/2-service-and-route/output-expected.yaml @@ -0,0 +1,53 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/input.yaml b/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/input.yaml new file mode 100644 index 000000000..0ee8eeadd --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/input.yaml @@ -0,0 +1,97 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/output-expected.yaml new file mode 100644 index 000000000..017f8f2d3 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/output-expected.yaml @@ -0,0 +1,105 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/input.yaml b/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/input.yaml new file mode 100644 index 000000000..9e4e5ec86 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/input.yaml @@ -0,0 +1,125 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/output-expected.yaml new file mode 100644 index 000000000..9baddd252 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/output-expected.yaml @@ -0,0 +1,136 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/input.yaml b/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/input.yaml new file mode 100644 index 000000000..e9fb52034 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: acl + config: + allow: + - admin +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + acls: + - group: acl_group + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/output-expected.yaml new file mode 100644 index 000000000..dc025e73b --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/output-expected.yaml @@ -0,0 +1,174 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + allow: + - admin +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: acl +plugin: acl +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: acl + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: acl-group-example-user +stringData: + group: acl_group + kongCredType: acl +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- acl-group-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/input.yaml b/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/input.yaml new file mode 100644 index 000000000..163846d4b --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: basic-auth + config: + hide_credentials: false +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + basicauth_credentials: + - username: my_basic_user + password: my_basic_password + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/output-expected.yaml new file mode 100644 index 000000000..918b21b62 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/output-expected.yaml @@ -0,0 +1,174 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_credentials: false +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: basic-auth +plugin: basic-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: basic-auth + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: basic-auth-example-user +stringData: + kongCredType: basic-auth + password: my_basic_password + username: my_basic_user +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- basic-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/input.yaml b/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/input.yaml new file mode 100644 index 000000000..363f14ea3 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/input.yaml @@ -0,0 +1,151 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: jwt + config: + uri_param_names: + - token + claims_to_verify: + - exp + - nbf + key_claim_name: kid + secret_is_base64: false + anonymous: null + run_on_preflight: true + maximum_expiration: 3600 + header_names: + - Authorization +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + jwt_secrets: + - key: my_jwt_secret + algorithm: HS256 + secret: my_secret_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml new file mode 100644 index 000000000..56265f57b --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml @@ -0,0 +1,186 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + anonymous: null + claims_to_verify: + - exp + - nbf + header_names: + - Authorization + key_claim_name: kid + maximum_expiration: 3600 + run_on_preflight: true + secret_is_base64: false + uri_param_names: + - token +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: jwt +plugin: jwt +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: jwt + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: jwt-auth-example-user +stringData: + algorithm: HS256 + key: my_jwt_secret + kongCredType: jwt + secret: my_secret_key +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- jwt-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/input.yaml b/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/input.yaml new file mode 100644 index 000000000..754bb520c --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/input.yaml @@ -0,0 +1,142 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: key-auth + config: + hide_credentials: false + key_names: + - apikey + key_in_body: false + run_on_preflight: true +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + keyauth_credentials: + - key: my_api_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/output-expected.yaml new file mode 100644 index 000000000..076148230 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/output-expected.yaml @@ -0,0 +1,177 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_credentials: false + key_in_body: false + key_names: + - apikey + run_on_preflight: true +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: key-auth +plugin: key-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: key-auth + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: key-auth-example-user +stringData: + key: my_api_key + kongCredType: key-auth +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- key-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/input.yaml b/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/input.yaml new file mode 100644 index 000000000..f12055648 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/input.yaml @@ -0,0 +1,140 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: mtls-auth + config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + skip_consumer_lookup: false + revocation_check_mode: SKIP +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + mtls_auth_credentials: + - id: cce8c384-721f-4f58-85dd-50834e3e733a + subject_name: example-user@example.com \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml new file mode 100644 index 000000000..640e3615b --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml @@ -0,0 +1,178 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + revocation_check_mode: SKIP + skip_consumer_lookup: false +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: mtls-auth +plugin: mtls-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: mtls-auth + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: mtls-auth-example-user +stringData: + id: cce8c384-721f-4f58-85dd-50834e3e733a + kongCredType: mtls-auth + subject_name: example-user@example.com +type: Opaque +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- mtls-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/json/1-service/input.yaml b/file/testdata/kong2kic/custom_resources/json/1-service/input.yaml new file mode 100644 index 000000000..2632c2c53 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/1-service/input.yaml @@ -0,0 +1,16 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/1-service/output-expected.json b/file/testdata/kong2kic/custom_resources/json/1-service/output-expected.json new file mode 100644 index 000000000..db9c52653 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/1-service/output-expected.json @@ -0,0 +1,44 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/input.yaml b/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/input.yaml new file mode 100644 index 000000000..316ff1b35 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/input.yaml @@ -0,0 +1,194 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: aws-lambda + config: + aws_key: my_key + aws_secret: my_secret + function_name: my_function + aws_region: us-west-2 + - name: cors + config: + origins: + - example.com + methods: + - GET + - POST + headers: + - Authorization + exposed_headers: + - X-My-Header + max_age: 3600 + credentials: true + - name: file-log + config: + path: /var/log/kong/kong.log + reopen: true + - name: http-log + config: + http_endpoint: http://example.com/logs + method: POST + content_type: application/json + timeout: 10000 + keepalive: 60000 + retry_count: 10 + queue_size: 1000 + - name: ip-restriction + config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 + - name: rate-limiting-advanced + config: + limit: + - 5 + window_size: + - 30 + identifier: consumer + sync_rate: -1 + namespace: example_namespace + strategy: local + hide_client_headers: false + - name: request-termination + config: + status_code: 403 + message: Forbidden + - name: response-ratelimiting + config: + limits: + limit_name: + minute: 10 + policy: local + - name: tcp-log + config: + host: example.com + port: 1234 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/output-expected.json b/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/output-expected.json new file mode 100644 index 000000000..152f3b8ae --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/output-expected.json @@ -0,0 +1,401 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "aws-lambda", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "aws_key": "my_key", + "aws_region": "us-west-2", + "aws_secret": "my_secret", + "function_name": "my_function" + }, + "plugin": "aws-lambda", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "cors", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "credentials": true, + "exposed_headers": [ + "X-My-Header" + ], + "headers": [ + "Authorization" + ], + "max_age": 3600, + "methods": [ + "GET", + "POST" + ], + "origins": [ + "example.com" + ] + }, + "plugin": "cors", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "file-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "path": "/var/log/kong/kong.log", + "reopen": true + }, + "plugin": "file-log", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "http-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "content_type": "application/json", + "http_endpoint": "http://example.com/logs", + "keepalive": 60000, + "method": "POST", + "queue_size": 1000, + "retry_count": 10, + "timeout": 10000 + }, + "plugin": "http-log", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "ip-restriction", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "allow": [ + "192.168.0.1/24" + ], + "deny": [ + "192.168.0.2/32" + ] + }, + "plugin": "ip-restriction", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "rate-limiting-advanced", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_client_headers": false, + "identifier": "consumer", + "limit": [ + 5 + ], + "namespace": "example_namespace", + "strategy": "local", + "sync_rate": -1, + "window_size": [ + 30 + ] + }, + "plugin": "rate-limiting-advanced", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "request-termination", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "message": "Forbidden", + "status_code": 403 + }, + "plugin": "request-termination", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "response-ratelimiting", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "limits": { + "limit_name": { + "minute": 10 + } + }, + "policy": "local" + }, + "plugin": "response-ratelimiting", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "tcp-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "host": "example.com", + "port": 1234 + }, + "plugin": "tcp-log", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "aws-lambda,cors,file-log,http-log,ip-restriction,rate-limiting-advanced,request-termination,response-ratelimiting,tcp-log" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/11-consumer-group/input.yaml b/file/testdata/kong2kic/custom_resources/json/11-consumer-group/input.yaml new file mode 100644 index 000000000..b57971e33 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/11-consumer-group/input.yaml @@ -0,0 +1,9 @@ +consumer_groups: + - name: example-consumer-group + consumers: + - username: example-user +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/11-consumer-group/output-expected.json b/file/testdata/kong2kic/custom_resources/json/11-consumer-group/output-expected.json new file mode 100644 index 000000000..3e8cf4b1e --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/11-consumer-group/output-expected.json @@ -0,0 +1,28 @@ +{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "consumerGroups": [ + "example-consumer-group" + ], + "status": {} +}{ + "kind": "KongConsumerGroup", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-consumer-group", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/2-service-and-route/input.yaml b/file/testdata/kong2kic/custom_resources/json/2-service-and-route/input.yaml new file mode 100644 index 000000000..96ffcce6e --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/2-service-and-route/input.yaml @@ -0,0 +1,44 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/2-service-and-route/output-expected.json b/file/testdata/kong2kic/custom_resources/json/2-service-and-route/output-expected.json new file mode 100644 index 000000000..86ae5d069 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/2-service-and-route/output-expected.json @@ -0,0 +1,117 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/input.yaml b/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/input.yaml new file mode 100644 index 000000000..0ee8eeadd --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/input.yaml @@ -0,0 +1,97 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/output-expected.json b/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/output-expected.json new file mode 100644 index 000000000..f226a23db --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/output-expected.json @@ -0,0 +1,133 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/input.yaml b/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/input.yaml new file mode 100644 index 000000000..9e4e5ec86 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/input.yaml @@ -0,0 +1,125 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/output-expected.json b/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/output-expected.json new file mode 100644 index 000000000..23d2b86d8 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/output-expected.json @@ -0,0 +1,206 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/input.yaml b/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/input.yaml new file mode 100644 index 000000000..e9fb52034 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: acl + config: + allow: + - admin +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + acls: + - group: acl_group + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/output-expected.json b/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/output-expected.json new file mode 100644 index 000000000..e2e70f0b5 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/output-expected.json @@ -0,0 +1,254 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "acl", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "allow": [ + "admin" + ] + }, + "plugin": "acl", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "acl" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "acl-group-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "group": "acl_group", + "kongCredType": "acl" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "acl-group-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/input.yaml b/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/input.yaml new file mode 100644 index 000000000..163846d4b --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: basic-auth + config: + hide_credentials: false +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + basicauth_credentials: + - username: my_basic_user + password: my_basic_password + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/output-expected.json b/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/output-expected.json new file mode 100644 index 000000000..0701fbb57 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/output-expected.json @@ -0,0 +1,253 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "basic-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_credentials": false + }, + "plugin": "basic-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "basic-auth" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "basic-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "kongCredType": "basic-auth", + "password": "my_basic_password", + "username": "my_basic_user" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "basic-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/input.yaml b/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/input.yaml new file mode 100644 index 000000000..363f14ea3 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/input.yaml @@ -0,0 +1,151 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: jwt + config: + uri_param_names: + - token + claims_to_verify: + - exp + - nbf + key_claim_name: kid + secret_is_base64: false + anonymous: null + run_on_preflight: true + maximum_expiration: 3600 + header_names: + - Authorization +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + jwt_secrets: + - key: my_jwt_secret + algorithm: HS256 + secret: my_secret_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/output-expected.json b/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/output-expected.json new file mode 100644 index 000000000..035d62dbf --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/output-expected.json @@ -0,0 +1,268 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "jwt", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "anonymous": null, + "claims_to_verify": [ + "exp", + "nbf" + ], + "header_names": [ + "Authorization" + ], + "key_claim_name": "kid", + "maximum_expiration": 3600, + "run_on_preflight": true, + "secret_is_base64": false, + "uri_param_names": [ + "token" + ] + }, + "plugin": "jwt", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "jwt" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "jwt-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "algorithm": "HS256", + "key": "my_jwt_secret", + "kongCredType": "jwt", + "secret": "my_secret_key" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "jwt-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/input.yaml b/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/input.yaml new file mode 100644 index 000000000..754bb520c --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/input.yaml @@ -0,0 +1,142 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: key-auth + config: + hide_credentials: false + key_names: + - apikey + key_in_body: false + run_on_preflight: true +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + keyauth_credentials: + - key: my_api_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/output-expected.json b/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/output-expected.json new file mode 100644 index 000000000..59f3c4dc2 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/output-expected.json @@ -0,0 +1,257 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "key-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_credentials": false, + "key_in_body": false, + "key_names": [ + "apikey" + ], + "run_on_preflight": true + }, + "plugin": "key-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "key-auth" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "key-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "key": "my_api_key", + "kongCredType": "key-auth" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "key-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/input.yaml b/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/input.yaml new file mode 100644 index 000000000..f12055648 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/input.yaml @@ -0,0 +1,140 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: mtls-auth + config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + skip_consumer_lookup: false + revocation_check_mode: SKIP +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + mtls_auth_credentials: + - id: cce8c384-721f-4f58-85dd-50834e3e733a + subject_name: example-user@example.com \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/output-expected.json b/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/output-expected.json new file mode 100644 index 000000000..22c01e94f --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/output-expected.json @@ -0,0 +1,258 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "mtls-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "ca_certificates": [ + "cce8c384-721f-4f58-85dd-50834e3e733a" + ], + "revocation_check_mode": "SKIP", + "skip_consumer_lookup": false + }, + "plugin": "mtls-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "mtls-auth" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "mtls-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "id": "cce8c384-721f-4f58-85dd-50834e3e733a", + "kongCredType": "mtls-auth", + "subject_name": "example-user@example.com" + }, + "type": "Opaque" +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "mtls-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/1-service/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/1-service/input.yaml new file mode 100644 index 000000000..2632c2c53 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/1-service/input.yaml @@ -0,0 +1,16 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/1-service/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/1-service/output-expected.yaml new file mode 100644 index 000000000..14aad8740 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/1-service/output-expected.yaml @@ -0,0 +1,32 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/input.yaml new file mode 100644 index 000000000..316ff1b35 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/input.yaml @@ -0,0 +1,194 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: aws-lambda + config: + aws_key: my_key + aws_secret: my_secret + function_name: my_function + aws_region: us-west-2 + - name: cors + config: + origins: + - example.com + methods: + - GET + - POST + headers: + - Authorization + exposed_headers: + - X-My-Header + max_age: 3600 + credentials: true + - name: file-log + config: + path: /var/log/kong/kong.log + reopen: true + - name: http-log + config: + http_endpoint: http://example.com/logs + method: POST + content_type: application/json + timeout: 10000 + keepalive: 60000 + retry_count: 10 + queue_size: 1000 + - name: ip-restriction + config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 + - name: rate-limiting-advanced + config: + limit: + - 5 + window_size: + - 30 + identifier: consumer + sync_rate: -1 + namespace: example_namespace + strategy: local + hide_client_headers: false + - name: request-termination + config: + status_code: 403 + message: Forbidden + - name: response-ratelimiting + config: + limits: + limit_name: + minute: 10 + policy: local + - name: tcp-log + config: + host: example.com + port: 1234 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/output-expected.yaml new file mode 100644 index 000000000..b951ff177 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/output-expected.yaml @@ -0,0 +1,312 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + aws_key: my_key + aws_region: us-west-2 + aws_secret: my_secret + function_name: my_function +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: aws-lambda +plugin: aws-lambda +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + credentials: true + exposed_headers: + - X-My-Header + headers: + - Authorization + max_age: 3600 + methods: + - GET + - POST + origins: + - example.com +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: cors +plugin: cors +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + path: /var/log/kong/kong.log + reopen: true +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: file-log +plugin: file-log +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + content_type: application/json + http_endpoint: http://example.com/logs + keepalive: 60000 + method: POST + queue_size: 1000 + retry_count: 10 + timeout: 10000 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: http-log +plugin: http-log +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: ip-restriction +plugin: ip-restriction +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_client_headers: false + identifier: consumer + limit: + - 5 + namespace: example_namespace + strategy: local + sync_rate: -1 + window_size: + - 30 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: rate-limiting-advanced +plugin: rate-limiting-advanced +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + message: Forbidden + status_code: 403 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: request-termination +plugin: request-termination +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + limits: + limit_name: + minute: 10 + policy: local +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: response-ratelimiting +plugin: response-ratelimiting +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + host: example.com + port: 1234 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: tcp-log +plugin: tcp-log +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: aws-lambda,cors,file-log,http-log,ip-restriction,rate-limiting-advanced,request-termination,response-ratelimiting,tcp-log + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: configuration.konghq.com/v1 +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/input.yaml new file mode 100644 index 000000000..b57971e33 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/input.yaml @@ -0,0 +1,9 @@ +consumer_groups: + - name: example-consumer-group + consumers: + - username: example-user +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/output-expected.yaml new file mode 100644 index 000000000..19827ed76 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/output-expected.yaml @@ -0,0 +1,22 @@ +apiVersion: configuration.konghq.com/v1 +consumerGroups: +- example-consumer-group +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- +apiVersion: configuration.konghq.com/v1 +kind: KongConsumerGroup +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-consumer-group +status: {} +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/input.yaml new file mode 100644 index 000000000..96ffcce6e --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/input.yaml @@ -0,0 +1,44 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/output-expected.yaml new file mode 100644 index 000000000..9d64b3a1d --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/output-expected.yaml @@ -0,0 +1,82 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/input.yaml new file mode 100644 index 000000000..0ee8eeadd --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/input.yaml @@ -0,0 +1,97 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/output-expected.yaml new file mode 100644 index 000000000..f53030b9c --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/output-expected.yaml @@ -0,0 +1,106 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/input.yaml new file mode 100644 index 000000000..9e4e5ec86 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/input.yaml @@ -0,0 +1,125 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/output-expected.yaml new file mode 100644 index 000000000..7092e95a8 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/output-expected.yaml @@ -0,0 +1,156 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/input.yaml new file mode 100644 index 000000000..e9fb52034 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: acl + config: + allow: + - admin +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + acls: + - group: acl_group + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/output-expected.yaml new file mode 100644 index 000000000..e9d346443 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/output-expected.yaml @@ -0,0 +1,194 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + allow: + - admin +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: acl +plugin: acl +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: acl + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: acl-group-example-user +stringData: + group: acl_group + kongCredType: acl +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- acl-group-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/input.yaml new file mode 100644 index 000000000..163846d4b --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: basic-auth + config: + hide_credentials: false +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + basicauth_credentials: + - username: my_basic_user + password: my_basic_password + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/output-expected.yaml new file mode 100644 index 000000000..023b013c4 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/output-expected.yaml @@ -0,0 +1,194 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_credentials: false +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: basic-auth +plugin: basic-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: basic-auth + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: basic-auth-example-user +stringData: + kongCredType: basic-auth + password: my_basic_password + username: my_basic_user +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- basic-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/input.yaml new file mode 100644 index 000000000..363f14ea3 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/input.yaml @@ -0,0 +1,151 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: jwt + config: + uri_param_names: + - token + claims_to_verify: + - exp + - nbf + key_claim_name: kid + secret_is_base64: false + anonymous: null + run_on_preflight: true + maximum_expiration: 3600 + header_names: + - Authorization +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + jwt_secrets: + - key: my_jwt_secret + algorithm: HS256 + secret: my_secret_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml new file mode 100644 index 000000000..f0861819f --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml @@ -0,0 +1,206 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + anonymous: null + claims_to_verify: + - exp + - nbf + header_names: + - Authorization + key_claim_name: kid + maximum_expiration: 3600 + run_on_preflight: true + secret_is_base64: false + uri_param_names: + - token +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: jwt +plugin: jwt +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: jwt + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: jwt-auth-example-user +stringData: + algorithm: HS256 + key: my_jwt_secret + kongCredType: jwt + secret: my_secret_key +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- jwt-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/input.yaml new file mode 100644 index 000000000..754bb520c --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/input.yaml @@ -0,0 +1,142 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: key-auth + config: + hide_credentials: false + key_names: + - apikey + key_in_body: false + run_on_preflight: true +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + keyauth_credentials: + - key: my_api_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/output-expected.yaml new file mode 100644 index 000000000..c28d85653 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/output-expected.yaml @@ -0,0 +1,197 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_credentials: false + key_in_body: false + key_names: + - apikey + run_on_preflight: true +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: key-auth +plugin: key-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: key-auth + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: key-auth-example-user +stringData: + key: my_api_key + kongCredType: key-auth +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- key-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/input.yaml new file mode 100644 index 000000000..f12055648 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/input.yaml @@ -0,0 +1,140 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: mtls-auth + config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + skip_consumer_lookup: false + revocation_check_mode: SKIP +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + mtls_auth_credentials: + - id: cce8c384-721f-4f58-85dd-50834e3e733a + subject_name: example-user@example.com \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml new file mode 100644 index 000000000..01bebbf8e --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml @@ -0,0 +1,198 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + revocation_check_mode: SKIP + skip_consumer_lookup: false +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: mtls-auth +plugin: mtls-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: mtls-auth + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: mtls-auth-example-user +stringData: + id: cce8c384-721f-4f58-85dd-50834e3e733a + kongCredType: mtls-auth + subject_name: example-user@example.com +type: Opaque +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- mtls-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/types.go b/file/types.go index 7c6aa44c5..d778ad5e5 100644 --- a/file/types.go +++ b/file/types.go @@ -9,6 +9,11 @@ import ( "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" + kicv1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1" + kicv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1" + k8scorev1 "k8s.io/api/core/v1" + k8snetv1 "k8s.io/api/networking/v1" + "sigs.k8s.io/yaml" ) // Format is a file format for Kong's configuration. @@ -23,6 +28,11 @@ const ( JSON = "JSON" // YAML if YAML file format. YAML = "YAML" + // KIC YAML and JSON file format for ingress controller. + KIC_YAML_CRD = "KIC_YAML_CRD" + KIC_JSON_CRD = "KIC_JSON_CRD" + KIC_YAML_ANNOTATION = "KIC_YAML_ANNOTATION" + KIC_JSON_ANNOTATION = "KIC_JSON_ANNOTATION" ) const ( @@ -321,19 +331,20 @@ type FPlugin struct { // foo is a shadow type of Plugin. // It is used for custom marshalling of plugin. type foo struct { - CreatedAt *int `json:"created_at,omitempty" yaml:"created_at,omitempty"` - ID *string `json:"id,omitempty" yaml:"id,omitempty"` - Name *string `json:"name,omitempty" yaml:"name,omitempty"` - InstanceName *string `json:"instance_name,omitempty" yaml:"instance_name,omitempty"` - Config kong.Configuration `json:"config,omitempty" yaml:"config,omitempty"` - Service string `json:"service,omitempty" yaml:",omitempty"` - Consumer string `json:"consumer,omitempty" yaml:",omitempty"` - Route string `json:"route,omitempty" yaml:",omitempty"` - Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` - RunOn *string `json:"run_on,omitempty" yaml:"run_on,omitempty"` - Ordering *kong.PluginOrdering `json:"ordering,omitempty" yaml:"ordering,omitempty"` - Protocols []*string `json:"protocols,omitempty" yaml:"protocols,omitempty"` - Tags []*string `json:"tags,omitempty" yaml:"tags,omitempty"` + CreatedAt *int `json:"created_at,omitempty" yaml:"created_at,omitempty"` + ID *string `json:"id,omitempty" yaml:"id,omitempty"` + Name *string `json:"name,omitempty" yaml:"name,omitempty"` + InstanceName *string `json:"instance_name,omitempty" yaml:"instance_name,omitempty"` + Config kong.Configuration `json:"config,omitempty" yaml:"config,omitempty"` + Service string `json:"service,omitempty" yaml:",omitempty"` + Consumer string `json:"consumer,omitempty" yaml:",omitempty"` + ConsumerGroup string `json:"consumer_group,omitempty" yaml:",omitempty"` + Route string `json:"route,omitempty" yaml:",omitempty"` + Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + RunOn *string `json:"run_on,omitempty" yaml:"run_on,omitempty"` + Ordering *kong.PluginOrdering `json:"ordering,omitempty" yaml:"ordering,omitempty"` + Protocols []*string `json:"protocols,omitempty" yaml:"protocols,omitempty"` + Tags []*string `json:"tags,omitempty" yaml:"tags,omitempty"` ConfigSource *string `json:"_config,omitempty" yaml:"_config,omitempty"` } @@ -379,6 +390,9 @@ func copyToFoo(p FPlugin) foo { if p.Plugin.Service != nil { f.Service = *p.Plugin.Service.ID } + if p.Plugin.ConsumerGroup != nil { + f.ConsumerGroup = *p.Plugin.ConsumerGroup.ID + } return f } @@ -428,6 +442,11 @@ func copyFromFoo(f foo, p *FPlugin) { ID: kong.String(f.Service), } } + if f.ConsumerGroup != "" { + p.ConsumerGroup = &kong.ConsumerGroup{ + ID: kong.String(f.ConsumerGroup), + } + } } // MarshalYAML is a custom marshal method to handle @@ -480,6 +499,9 @@ func (p FPlugin) sortKey() string { if p.Service != nil { key += *p.Service.ID } + if p.ConsumerGroup != nil { + key += *p.ConsumerGroup.ID + } return key } if p.ID != nil { @@ -728,3 +750,201 @@ type Content struct { Licenses []FLicense `json:"licenses,omitempty" yaml:"licenses,omitempty"` } + +// KICContent represents a serialized Kong state for KIC. +// +k8s:deepcopy-gen=true +type KICContent struct { + KongIngresses []kicv1.KongIngress `json:"kongIngresses,omitempty" yaml:",omitempty"` + KongPlugins []kicv1.KongPlugin `json:"kongPlugins,omitempty" yaml:",omitempty"` + KongClusterPlugins []kicv1.KongClusterPlugin `json:"clusterPlugins,omitempty" yaml:",omitempty"` + Ingresses []k8snetv1.Ingress `json:"ingresses,omitempty" yaml:",omitempty"` + Services []k8scorev1.Service `json:"services,omitempty" yaml:",omitempty"` + Secrets []k8scorev1.Secret `json:"secrets,omitempty" yaml:",omitempty"` + KongConsumers []kicv1.KongConsumer `json:"consumers,omitempty" yaml:",omitempty"` + KongConsumerGroups []kicv1beta1.KongConsumerGroup `json:"consumerGroups,omitempty" yaml:",omitempty"` +} + +func (k KICContent) marshalKICContentToYaml() ([]byte, error) { + + var kongIngresses []byte + var kongPlugins []byte + var kongClusterPlugins []byte + var ingresses []byte + var services []byte + var secrets []byte + var kongConsumers []byte + var kongConsumerGroups []byte + var err error + var output []byte + + // iterate over the slices of kongIngresses, kongPlugins, + // kongClusterPlugins, ingresses, services, secrets, kongConsumers + // and marshal each one in yaml format + // and append it to the output slice + // then return the output slice + for _, kongIngress := range k.KongIngresses { + kongIngresses, err = yaml.Marshal(kongIngress) + if err != nil { + return nil, err + } + output = append(output, kongIngresses...) + output = append(output, []byte("---\n")...) + } + + for _, kongPlugin := range k.KongPlugins { + kongPlugins, err = yaml.Marshal(kongPlugin) + if err != nil { + return nil, err + } + output = append(output, kongPlugins...) + output = append(output, []byte("---\n")...) + + } + + for _, kongClusterPlugin := range k.KongClusterPlugins { + kongClusterPlugins, err = yaml.Marshal(kongClusterPlugin) + if err != nil { + return nil, err + } + output = append(output, kongClusterPlugins...) + output = append(output, []byte("---\n")...) + + } + + for _, ingress := range k.Ingresses { + ingresses, err = yaml.Marshal(ingress) + if err != nil { + return nil, err + } + output = append(output, ingresses...) + output = append(output, []byte("---\n")...) + + } + + for _, service := range k.Services { + services, err = yaml.Marshal(service) + if err != nil { + return nil, err + } + output = append(output, services...) + output = append(output, []byte("---\n")...) + + } + + for _, secret := range k.Secrets { + secrets, err = yaml.Marshal(secret) + if err != nil { + return nil, err + } + output = append(output, secrets...) + output = append(output, []byte("---\n")...) + + } + + for _, kongConsumer := range k.KongConsumers { + kongConsumers, err = yaml.Marshal(kongConsumer) + if err != nil { + return nil, err + } + output = append(output, kongConsumers...) + output = append(output, []byte("---\n")...) + + } + + for _, kongConsumerGroup := range k.KongConsumerGroups { + kongConsumerGroups, err = yaml.Marshal(kongConsumerGroup) + if err != nil { + return nil, err + } + output = append(output, kongConsumerGroups...) + output = append(output, []byte("---\n")...) + } + + return output, nil +} + +func (k KICContent) marshalKICContentToJson() ([]byte, error) { + + var kongIngresses []byte + var kongPlugins []byte + var kongClusterPlugins []byte + var ingresses []byte + var services []byte + var secrets []byte + var kongConsumers []byte + var kongConsumerGroups []byte + var err error + var output []byte + + // iterate over the slices of kongIngresses, kongPlugins, + // kongClusterPlugins, ingresses, services, secrets, kongConsumers + // and marshal each one in json format + // and append it to the output slice + // then return the output slice + for _, kongIngress := range k.KongIngresses { + kongIngresses, err = json.MarshalIndent(kongIngress, "", " ") + if err != nil { + return nil, err + } + output = append(output, kongIngresses...) + } + + for _, kongPlugin := range k.KongPlugins { + kongPlugins, err = json.MarshalIndent(kongPlugin, "", " ") + if err != nil { + return nil, err + } + output = append(output, kongPlugins...) + } + + for _, kongClusterPlugin := range k.KongClusterPlugins { + kongClusterPlugins, err = json.MarshalIndent(kongClusterPlugin, "", " ") + if err != nil { + return nil, err + } + output = append(output, kongClusterPlugins...) + } + + for _, ingress := range k.Ingresses { + ingresses, err = json.MarshalIndent(ingress, "", " ") + if err != nil { + return nil, err + } + output = append(output, ingresses...) + } + + for _, service := range k.Services { + services, err = json.MarshalIndent(service, "", " ") + if err != nil { + return nil, err + } + output = append(output, services...) + } + + for _, secret := range k.Secrets { + secrets, err = json.MarshalIndent(secret, "", " ") + if err != nil { + return nil, err + } + output = append(output, secrets...) + } + + for _, kongConsumer := range k.KongConsumers { + kongConsumers, err = json.MarshalIndent(kongConsumer, "", " ") + if err != nil { + return nil, err + } + output = append(output, kongConsumers...) + } + + for _, kongConsumerGroup := range k.KongConsumerGroups { + kongConsumerGroups, err = json.MarshalIndent(kongConsumerGroup, "", " ") + if err != nil { + return nil, err + } + output = append(output, kongConsumerGroups...) + } + + return output, nil +} + diff --git a/file/validate.go b/file/validate.go index 14076950c..ac2c3252b 100644 --- a/file/validate.go +++ b/file/validate.go @@ -1,6 +1,8 @@ package file import ( + "encoding/json" + "errors" "fmt" "github.com/kong/deck/utils" @@ -8,6 +10,15 @@ import ( "sigs.k8s.io/yaml" ) +type ValidationError struct { + Object string `json:"object"` + Err error `json:"error"` +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("validation error: object=%s, err=%v", e.Object, e.Err) +} + func validate(content []byte) error { var c map[string]interface{} err := yaml.Unmarshal(content, &c) @@ -24,10 +35,14 @@ func validate(content []byte) error { if result.Valid() { return nil } + var errs utils.ErrArray for _, desc := range result.Errors() { - err := fmt.Errorf(desc.String()) - errs.Errors = append(errs.Errors, err) + jsonString, err := json.Marshal(desc.Value()) + if err != nil { + return err + } + errs.Errors = append(errs.Errors, &ValidationError{Object: string(jsonString), Err: errors.New(desc.String())}) } return errs } @@ -41,3 +56,13 @@ func validateWorkspaces(workspaces []string) error { } return nil } + +func validateRuntimeGroups(names []string) error { + utils.RemoveDuplicates(&names) + if len(names) > 1 { + return fmt.Errorf("it seems like you are trying to sync multiple Konnect Runtime Groups "+ + "at the same time (%v).\ndecK doesn't support syncing multiple Runtime Groups at the same time, "+ + "please sync one Runtime Group at a time", names) + } + return nil +} diff --git a/file/writer.go b/file/writer.go index 4905f046c..4a08e5144 100644 --- a/file/writer.go +++ b/file/writer.go @@ -43,16 +43,16 @@ func getFormatVersion(kongVersion string) (string, error) { return formatVersion, nil } -// KongStateToFile writes a state object to file with filename. +// KongStateToFile generates a state object to file.Content. // It will omit timestamps and IDs while writing. -func KongStateToFile(kongState *state.KongState, config WriteConfig) error { +func KongStateToContent(kongState *state.KongState, config WriteConfig) (*Content, error) { file := &Content{} var err error file.Workspace = config.Workspace formatVersion, err := getFormatVersion(config.KongVersion) if err != nil { - return fmt.Errorf("get format version: %w", err) + return nil, fmt.Errorf("get format version: %w", err) } file.FormatVersion = formatVersion if config.RuntimeGroupName != "" { @@ -70,49 +70,58 @@ func KongStateToFile(kongState *state.KongState, config WriteConfig) error { err = populateServices(kongState, file, config) if err != nil { - return err + return nil, err } err = populateServicelessRoutes(kongState, file, config) if err != nil { - return err + return nil, err } err = populatePlugins(kongState, file, config) if err != nil { - return err + return nil, err } err = populateUpstreams(kongState, file, config) if err != nil { - return err + return nil, err } err = populateCertificates(kongState, file, config) if err != nil { - return err + return nil, err } err = populateCACertificates(kongState, file, config) if err != nil { - return err + return nil, err } err = populateConsumers(kongState, file, config) if err != nil { - return err + return nil, err } err = populateVaults(kongState, file, config) if err != nil { - return err + return nil, err } err = populateConsumerGroups(kongState, file, config) if err != nil { - return err + return nil, err } + return file, nil +} +// KongStateToFile writes a state object to file with filename. +// See KongStateToContent for the State generation +func KongStateToFile(kongState *state.KongState, config WriteConfig) error { + file, err := KongStateToContent(kongState, config) + if err != nil { + return err + } return WriteContentToFile(file, config.Filename, config.FileFormat) } @@ -382,7 +391,7 @@ func populatePlugins(kongState *state.KongState, file *Content, if p.Consumer != nil { associations++ cID := *p.Consumer.ID - consumer, err := kongState.Consumers.Get(cID) + consumer, err := kongState.Consumers.GetByIDOrUsername(cID) if err != nil { return fmt.Errorf("unable to get consumer %s for plugin %s [%s]: %w", cID, *p.Name, *p.ID, err) } @@ -415,6 +424,18 @@ func populatePlugins(kongState *state.KongState, file *Content, } p.Route.ID = &rID } + if p.ConsumerGroup != nil { + associations++ + cgID := *p.ConsumerGroup.ID + cg, err := kongState.ConsumerGroups.Get(cgID) + if err != nil { + return fmt.Errorf("unable to get consumer-group %s for plugin %s [%s]: %w", cgID, *p.Name, *p.ID, err) + } + if !utils.Empty(cg.Name) { + cgID = *cg.Name + } + p.ConsumerGroup.ID = &cgID + } if associations == 0 || associations > 1 { utils.ZeroOutID(p, p.Name, config.WithID) utils.ZeroOutTimestamps(p) @@ -703,13 +724,13 @@ func populateConsumerGroups(kongState *state.KongState, file *Content, if err != nil { return err } - plugins, err := kongState.ConsumerGroupPlugins.GetAll() + cgPlugins, err := kongState.ConsumerGroupPlugins.GetAll() if err != nil { return err } for _, cg := range consumerGroups { group := FConsumerGroupObject{ConsumerGroup: cg.ConsumerGroup} - for _, plugin := range plugins { + for _, plugin := range cgPlugins { if plugin.ID != nil && cg.ID != nil { if plugin.ConsumerGroup != nil && *plugin.ConsumerGroup.ID == *cg.ID { utils.ZeroOutID(plugin, plugin.Name, config.WithID) @@ -720,6 +741,25 @@ func populateConsumerGroups(kongState *state.KongState, file *Content, } } } + + plugins, err := kongState.Plugins.GetAllByConsumerGroupID(*cg.ID) + if err != nil { + return err + } + for _, plugin := range plugins { + if plugin.ID != nil && cg.ID != nil { + if plugin.ConsumerGroup != nil && *plugin.ConsumerGroup.ID == *cg.ID { + utils.ZeroOutID(plugin, plugin.Name, config.WithID) + utils.ZeroOutID(plugin.ConsumerGroup, plugin.ConsumerGroup.Name, config.WithID) + group.Plugins = append(group.Plugins, &kong.ConsumerGroupPlugin{ + ID: plugin.ID, + Name: plugin.Name, + Config: plugin.Config, + }) + } + } + } + utils.ZeroOutID(&group, group.Name, config.WithID) utils.ZeroOutTimestamps(&group) file.ConsumerGroups = append(file.ConsumerGroups, group) @@ -744,6 +784,26 @@ func WriteContentToFile(content *Content, filename string, format Format) error if err != nil { return err } + case KIC_JSON_CRD: + c, err = MarshalKongToKICJson(content, CUSTOM_RESOURCE) + if err != nil { + return err + } + case KIC_YAML_CRD: + c, err = MarshalKongToKICYaml(content, CUSTOM_RESOURCE) + if err != nil { + return err + } + case KIC_JSON_ANNOTATION: + c, err = MarshalKongToKICJson(content, ANNOTATIONS) + if err != nil { + return err + } + case KIC_YAML_ANNOTATION: + c, err = MarshalKongToKICYaml(content, ANNOTATIONS) + if err != nil { + return err + } default: return fmt.Errorf("unknown file format: " + string(format)) } diff --git a/file/writer_test.go b/file/writer_test.go index fea1cb0f7..12acc5931 100644 --- a/file/writer_test.go +++ b/file/writer_test.go @@ -402,4 +402,4 @@ func Test_getFormatVersion(t *testing.T) { } }) } -} +} \ No newline at end of file diff --git a/go.mod b/go.mod index 33178cc5d..a3e69f8d9 100644 --- a/go.mod +++ b/go.mod @@ -12,83 +12,113 @@ require ( github.com/fatih/color v1.15.0 github.com/google/go-cmp v0.5.9 github.com/google/go-querystring v1.1.0 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.3.1 github.com/hashicorp/go-memdb v1.3.4 - github.com/hashicorp/go-retryablehttp v0.7.2 + github.com/hashicorp/go-retryablehttp v0.7.4 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.16 - github.com/kong/go-kong v0.44.0 + github.com/kong/go-apiops v0.1.20 + github.com/kong/go-kong v0.46.0 github.com/mitchellh/go-homedir v1.1.0 - github.com/shirou/gopsutil/v3 v3.23.4 + github.com/shirou/gopsutil/v3 v3.23.7 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 github.com/ssgelm/cookiejarparser v1.0.1 github.com/stretchr/testify v1.8.4 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/sync v0.2.0 - k8s.io/code-generator v0.27.3 + golang.org/x/sync v0.3.0 + k8s.io/code-generator v0.28.0 sigs.k8s.io/yaml v1.3.0 ) require ( github.com/Kong/go-diff v1.2.2 // indirect - github.com/adrg/strutil v0.2.3 // indirect + github.com/adrg/strutil v0.3.0 // indirect + github.com/cilium/ebpf v0.11.0 // indirect + github.com/cosiner/argv v0.1.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d // indirect + github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 // indirect + github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/getkin/kin-openapi v0.108.0 // indirect + github.com/go-delve/delve v1.21.0 // indirect + github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // 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/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-dap v0.11.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/yaml v0.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kong/kubernetes-ingress-controller/v2 v2.11.1 // indirect github.com/kong/semver/v4 v4.0.1 // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mozillazg/go-slugify v0.2.0 // indirect + github.com/mozillazg/go-unidecode v0.2.0 // indirect + github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/shoenig/go-m1cpu v0.1.5 // indirect + github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/satori/go.uuid v1.2.0 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect github.com/tklauser/go-sysconf v0.3.11 // indirect - github.com/tklauser/numcpus v0.6.0 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect - github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect - golang.org/x/tools v0.7.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect + golang.org/x/tools v0.11.1 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.28.1 // indirect + k8s.io/apiextensions-apiserver v0.28.0 // indirect + k8s.io/apimachinery v0.28.1 // indirect k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect - k8s.io/klog/v2 v2.90.1 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/controller-runtime v0.15.1 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect ) diff --git a/go.sum b/go.sum index 8ca207b0a..9279e6c96 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,7 @@ cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK 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.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= 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= @@ -25,6 +26,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g 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= @@ -35,68 +37,316 @@ 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= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= +contrib.go.opencensus.io/exporter/prometheus v0.2.1-0.20200609204449-6bcf6f8577f0/go.mod h1:MjHoxkI7Ny27toPeFkRbXbzVjzIGkwOAptrAy8Mxtm8= +contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= +contrib.go.opencensus.io/exporter/zipkin v0.1.2/go.mod h1:mP5xM3rrgOjpn79MM8fZbj3gsxcuytSqtH0dxSWW1RE= 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-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +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/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kong/go-diff v1.2.2 h1:KKKaqHc8IxuguFVIZMNt3bi6YuC/t9r7BGD8bOOpSgM= github.com/Kong/go-diff v1.2.2/go.mod h1:nlvdwVZQk3Rm+tbI0cDmKFrOjghtcZTrZBp+UruvvA8= +github.com/Kong/gojsondiff v1.3.0/go.mod h1:dbd2hU4/ecVIVMCKypBnQ5/ol5ALWb1f+XNJFE25j+c= github.com/Kong/gojsondiff v1.3.2 h1:qIOVq2mUXt+NXy8Be5gRUee9TP3Ve0MbQSafg9bXKZE= github.com/Kong/gojsondiff v1.3.2/go.mod h1:DiIxtU59q4alK7ecP+7k56C5UjgOviJ5gQVR2esEhYw= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/adrg/strutil v0.2.3 h1:WZVn3ItPBovFmP4wMHHVXUr8luRaHrbyIuLlHt32GZQ= github.com/adrg/strutil v0.2.3/go.mod h1:+SNxbiH6t+O+5SZqIj5n/9i5yUjR+S3XXVrjEcN2mxg= +github.com/adrg/strutil v0.3.0 h1:bi/HB2zQbDihC8lxvATDTDzkT4bG7PATtVnDYp5rvq4= +github.com/adrg/strutil v0.3.0/go.mod h1:Jz0wzBVE6Uiy9wxo62YEqEY1Nwto3QlLl1Il5gkLKWU= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/alecthomas/jsonschema v0.0.0-20180308105923-f2c93856175a/go.mod h1:qpebaTNSsyUn5rPSJMsfqEtDw71TTggXM6stUDI16HA= github.com/alecthomas/jsonschema v0.0.0-20191017121752-4bb6e3fae4f2 h1:swGeCLPiUQ647AIRnFxnAHdzlg6IPpmU6QdkOPZINt8= github.com/alecthomas/jsonschema v0.0.0-20191017121752-4bb6e3fae4f2/go.mod h1:Juc2PrI3wtNfUwptSvAIeNx+HrETwHQs6nf+TkOJlOA= +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/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +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/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.31.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +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/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.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= +github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q= +github.com/bombsimon/logrusr v1.1.0/go.mod h1:Jq0nHtvxabKE5EMwAAdgTaz7dfWE8C4i11NOltxGQpc= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +github.com/cespare/xxhash/v2 v2.1.1/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/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= +github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= 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-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +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/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/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg= +github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +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.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/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/derekparker/trie v0.0.0-20221213183930-4c74548207f4/go.mod h1:C7Es+DLenIpPc9J6IYw4jrK0h7S9bKj4DNl8+KxGEXU= +github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d h1:hUWoLdw5kvo2xCsqlsIBMvWUc1QCSsCYD2J2+Fg6YoU= +github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d/go.mod h1:C7Es+DLenIpPc9J6IYw4jrK0h7S9bKj4DNl8+KxGEXU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-gk v0.0.0-20140819190930-201884a44051/go.mod h1:qm+vckxRlDt0aOla0RYJJVeqHZlWfOm2UIxHaqPB46E= +github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654/go.mod h1:qm+vckxRlDt0aOla0RYJJVeqHZlWfOm2UIxHaqPB46E= +github.com/dgryski/go-lttb v0.0.0-20180810165845-318fcdf10a77/go.mod h1:Va5MyIzkU0rAM92tn3hb3Anb7oz7KcnixF49+2wOMe4= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 h1:aRd8M7HJVZOqn/vhOzrGcQH0lNAMkqMn+pXUYkatmcA= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +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/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +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/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 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.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 v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.11.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +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/getkin/kin-openapi v0.108.0 h1:EYf0GtsKa4hQNIlplGS+Au7NEfGQ1F7MoHD2kcVevPQ= +github.com/getkin/kin-openapi v0.108.0/go.mod h1:QtwUNt0PAAgIIBEvFWYfB7dfngxtAaqCX1zYHMZDeK8= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-delve/delve v1.21.0 h1:npcc8TZhdVxaMSJon+zqcE3bXM/ck8SSOOWw/id13jI= +github.com/go-delve/delve v1.21.0/go.mod h1:U+OAdfhewudkHsVs/AwhfpSBu7t/NgIXH3+my4T5q78= +github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d h1:pxjSLshkZJGLVm0wv20f/H0oTWiq/egkoJQ2ja6LEvo= +github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d/go.mod h1:biJCRbqp51wS+I92HMqn5H8/A0PAhxn2vyOT+JqhiGI= 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/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +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 v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= +github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 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/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +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/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= 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/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +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/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +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.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 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-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v2.20.0+incompatible h1:4Xh3bDzO29j4TWNOI+24ubc0vbVFMg2PMnXKxK54/CA= +github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/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/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/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-20191027212112-611e8accdfc9/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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -124,10 +374,22 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= +github.com/gonum/diff v0.0.0-20181124234638-500114f11e71/go.mod h1:22dM4PLscQl+Nzf64qNBurVJvfyvZELT0iRW2l/NN70= +github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= +github.com/gonum/integrate v0.0.0-20181209220457-a422b5c0fdf2/go.mod h1:pDgmNM6seYpwvPos3q+zxlXMsbve6mOIPucUnUOrI7Y= +github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= +github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= +github.com/gonum/mathext v0.0.0-20181121095525-8a4bf007ea55/go.mod h1:fmo8aiSEWkJeiGXUJf+sPvuDgEFgqIoZSs843ePKrGg= +github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= +github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs= 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/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +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= @@ -136,16 +398,25 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/go-dap v0.9.1/go.mod h1:HAeyoSd2WIfTfg+0GRXcFrb+RnojAtGNh+k+XTIxJDE= +github.com/google/go-dap v0.11.0 h1:SpAZJL41rOOvd85PuLCCLE1dteTQOyKNnn0H3DBHywo= +github.com/google/go-dap v0.11.0/go.mod h1:HAeyoSd2WIfTfg+0GRXcFrb+RnojAtGNh+k+XTIxJDE= +github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/mako v0.0.0-20190821191249-122f8dcef9e3/go.mod h1:YzLcVlL+NqWnmUEPuhS1LxDDwGO9WNbVlEXaF4IH35g= 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= @@ -161,117 +432,412 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/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= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/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/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +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-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.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= +github.com/grpc-ecosystem/grpc-gateway v1.14.8/go.mod h1:NZE8t6vs6TnwLL/ITkaK8W3ecMLGAbh2jXTclvpiwYo= +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/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +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-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.2/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c= github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= -github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= -github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +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-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= +github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +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 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 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/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= 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.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/tdigest v0.0.0-20180711151920-a7d76c6f093a/go.mod h1:9GkyshztGufsdPQWjH+ifgnIr3xNUL5syI70g2dzU1o= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= +github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 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.8/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/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/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +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/kong/go-kong v0.44.0 h1:1x3w/TYdJjIZ6c1j9HiYP8755c923XN2O6j3kEaUkTA= -github.com/kong/go-kong v0.44.0/go.mod h1:41Sot1N/n8UHBp+gE/6nOw3vuzoHbhMSyU/zOS7VzPE= +github.com/kong/go-apiops v0.1.20 h1:MesWbep8Jvnk1FlbaFWgv4IkSi+DNojlSVmFt8INzrc= +github.com/kong/go-apiops v0.1.20/go.mod h1:3P9DBGLcU6Gp4wo8z4xohcg8PMutBAknc54pLZoQtDs= +github.com/kong/go-kong v0.46.0 h1:9I6nlX63WymU5Sg+d13iZDVwpW5vXh8/v0zarU27dzI= +github.com/kong/go-kong v0.46.0/go.mod h1:41Sot1N/n8UHBp+gE/6nOw3vuzoHbhMSyU/zOS7VzPE= +github.com/kong/kubernetes-ingress-controller/v2 v2.11.1 h1:ICadZZUXTEUE1O9ATVvB8ItNdHEQkF/SANv/CnLUbTw= +github.com/kong/kubernetes-ingress-controller/v2 v2.11.1/go.mod h1:BA6BvTy6b45Hw4nK5Oj6b29X8YjOPB8krhTgPr+hEQI= github.com/kong/semver/v4 v4.0.1 h1:DIcNR8W3gfx0KabFBADPalxxsp+q/5COwIFkkhrFQ2Y= github.com/kong/semver/v4 v4.0.1/go.mod h1:LImQ0oT15pJvSns/hs2laLca2zcYoHu5EsSNY0J6/QA= +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.2/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/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/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +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.1-0.20191009090205-6c0755d89d1e/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +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-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +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/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.17/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/48M= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 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.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= 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/onsi/ginkgo v1.1.1-0.20150303023352-38caab951a9f h1:znMoXRti03ZIy/gaW5cl3EO2A5zqzHBKA6uMnrAQDE0= -github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= -github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mozillazg/go-slugify v0.2.0 h1:SIhqDlnJWZH8OdiTmQgeXR28AOnypmAXPeOTcG7b9lk= +github.com/mozillazg/go-slugify v0.2.0/go.mod h1:z7dPH74PZf2ZPFkyxx+zjPD8CNzRJNa1CGacv0gg8Ns= +github.com/mozillazg/go-unidecode v0.2.0 h1:vFGEzAH9KSwyWmXCOblazEWDh7fOkpmy/Z4ArmamSUc= +github.com/mozillazg/go-unidecode v0.2.0/go.mod h1:zB48+/Z5toiRolOZy9ksLryJ976VIwmDmpQ2quyt1aA= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +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/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +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/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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.1.1-0.20150303023352-38caab951a9f/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2 h1:uqH7bpe+ERSiDa34FDOF7RikN6RzXgduUF8yarlZp94= +github.com/onsi/ginkgo v1.10.2/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.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= +github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= +github.com/onsi/gomega v0.0.0-20150401040250-4dfabf7db2e4/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +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.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +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-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +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.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/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= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +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.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +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.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.25.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/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.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +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/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rs/dnscache v0.0.0-20210201191234-295bba877686/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o= -github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8= -github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ= -github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ= -github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c= -github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= +github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +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.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +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/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.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +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.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/ssgelm/cookiejarparser v1.0.1 h1:cRdXauUbOTFzTPJFaeiWbHnQ+tRGlpKKzvIK9PUekE4= github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= 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/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -279,26 +845,48 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= +github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +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/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= +github.com/tsenart/vegeta/v12 v12.8.4/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli v1.19.2-0.20170215051705-2526b57c56f3/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -306,29 +894,76 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yudai/golcs v0.0.0-20150405163532-d1c525dea8ce/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yudai/pp v2.0.2-0.20150410014804-be8315415630+incompatible h1:TmF93o7P81230DTx1l2zw5rZbsDpOOQXoKVCa8+nXXI= +github.com/yudai/pp v2.0.2-0.20150410014804-be8315415630+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= 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/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 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-0.20200608061201-1901b56b9515/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.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.starlark.net v0.0.0-20220816155156-cfacd8902214/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk= +go.starlark.net v0.0.0-20230831151029-c9e9adf3fde2 h1:JVsWByUy+MOggHrZ2zLfLUBOcinE2w18iSbLb7WAAIc= +go.starlark.net v0.0.0-20230831151029-c9e9adf3fde2/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +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.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= +golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= +golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +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-20190320223903-b7391e95e576/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-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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= @@ -339,6 +974,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 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/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= 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= @@ -360,22 +997,42 @@ 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.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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-20181005035420-146acd28ed58/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-20190125091013-d26f9f9a57f3/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-20190320064053-1272bf9dcd53/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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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-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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/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= @@ -387,18 +1044,28 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ 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-20201224014010-6772e930b67b/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= 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= @@ -418,23 +1085,45 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ 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.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20181122145206-62eef0e2fa9b/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-20190321052220-f7bb7a8bee54/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-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-20190916202348-b4ddaad3f8a3/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-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/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-20191220142924-d4481acd189f/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/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= @@ -444,54 +1133,99 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/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-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/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-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/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-20210423185535-09eb48e85fd7/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-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-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.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-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/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= 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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/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-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-20181011042414-1f849cf54d09/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-20190125232054-d66bd3c5d5a6/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-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/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-20190907020128-2ca718005c18/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-20191010075000-0337d82405ff/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-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/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= @@ -499,6 +1233,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn 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-20200103221440-774c71fcf114/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= @@ -514,7 +1249,9 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY 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-20200616133436-c1934b75d054/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= @@ -522,19 +1259,34 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u 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.0.0-20210108195828-e2f9c7f1fc8e/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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc= +golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 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.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 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.10.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= @@ -544,6 +1296,7 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ 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.25.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= @@ -551,9 +1304,11 @@ google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ 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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 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.2/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/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -562,6 +1317,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn 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-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 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= @@ -580,8 +1336,10 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/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-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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= @@ -589,15 +1347,22 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D 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-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= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/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= @@ -607,9 +1372,12 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji 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.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= @@ -622,25 +1390,59 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.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/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/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/evanphx/json-patch.v4 v4.9.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/pool.v3 v3.1.1/go.mod h1:pUAGBximS/hccTTSzEop6wvvQhVa3QPDFFW+8REdutg= +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/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.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/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +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= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 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= @@ -648,21 +1450,74 @@ 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/code-generator v0.27.3 h1:JRhRQkzKdQhHmv9s5f7vuqveL8qukAQ2IqaHm6MFspM= -k8s.io/code-generator v0.27.3/go.mod h1:DPung1sI5vBgn4AGKtlPRQAyagj/ir/4jI55ipZHVww= +k8s.io/api v0.28.0/go.mod h1:0l8NZJzB0i/etuWnIXcwfIv+xnDOhL3lLW919AWYDuY= +k8s.io/api v0.28.1 h1:i+0O8k2NPBCPYaMB+uCkseEbawEt/eFaiRqUx8aB108= +k8s.io/api v0.28.1/go.mod h1:uBYwID+66wiL28Kn2tBjBYQdEU0Xk0z5qF8bIBqk/Dg= +k8s.io/apiextensions-apiserver v0.28.0 h1:CszgmBL8CizEnj4sj7/PtLGey6Na3YgWyGCPONv7E9E= +k8s.io/apiextensions-apiserver v0.28.0/go.mod h1:uRdYiwIuu0SyqJKriKmqEN2jThIJPhVmOWETm8ud1VE= +k8s.io/apimachinery v0.28.0 h1:ScHS2AG16UlYWk63r46oU3D5y54T53cVI5mMJwwqFNA= +k8s.io/apimachinery v0.28.0/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= +k8s.io/apimachinery v0.28.1 h1:EJD40og3GizBSV3mkIoXQBsws32okPOy+MkRyzh6nPY= +k8s.io/apimachinery v0.28.1/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= +k8s.io/code-generator v0.27.4 h1:bw2xFEBnthhCSC7Bt6FFHhPTfWX21IJ30GXxOzywsFE= +k8s.io/code-generator v0.27.4/go.mod h1:DPung1sI5vBgn4AGKtlPRQAyagj/ir/4jI55ipZHVww= +k8s.io/code-generator v0.28.0 h1:msdkRVJNVFgdiIJ8REl/d3cZsMB9HByFcWMmn13NyuE= +k8s.io/code-generator v0.28.0/go.mod h1:ueeSJZJ61NHBa0ccWLey6mwawum25vX61nRZ6WOzN9A= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d h1:U9tB195lKdzwqicbJvyJeOXV7Klv+wNAWENRnXEGi08= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +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.4.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/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= 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/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +knative.dev/hack v0.0.0-20210203173706-8368e1f6eacf/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI= +knative.dev/networking v0.0.0-20210216014426-94bfc013982b/go.mod h1:Crdn87hxdFd3Jj6PIyrjzGnr8OGHX35k5xo9jlOrjjA= +knative.dev/pkg v0.0.0-20210215165523-84c98f3c3e7a/go.mod h1:TJSdebQOWX5N2bszohOYVi0H1QtXbtlYLuMghAFBMhY= +knative.dev/pkg v0.0.0-20210216013737-584933f8280b/go.mod h1:TJSdebQOWX5N2bszohOYVi0H1QtXbtlYLuMghAFBMhY= +pgregory.net/rapid v0.3.3/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 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.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/controller-runtime v0.8.3/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU= +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/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c= +sigs.k8s.io/controller-runtime v0.15.1/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.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= 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.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 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= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/state/builder.go b/state/builder.go index cf7e1c20a..97526dada 100644 --- a/state/builder.go +++ b/state/builder.go @@ -46,7 +46,7 @@ func ensureRoute(kongState *KongState, routeID string) (bool, *kong.Route, error } func ensureConsumer(kongState *KongState, consumerID string) (bool, *kong.Consumer, error) { - c, err := kongState.Consumers.Get(consumerID) + c, err := kongState.Consumers.GetByIDOrUsername(consumerID) if err != nil { if errors.Is(err, ErrNotFound) { return false, nil, nil @@ -57,6 +57,18 @@ func ensureConsumer(kongState *KongState, consumerID string) (bool, *kong.Consum return true, utils.GetConsumerReference(c.Consumer), nil } +func ensureConsumerGroup(kongState *KongState, consumerGroupID string) (bool, *kong.ConsumerGroup, error) { + c, err := kongState.ConsumerGroups.Get(consumerGroupID) + if err != nil { + if errors.Is(err, ErrNotFound) { + return false, nil, nil + } + return false, nil, fmt.Errorf("looking up consumer-group %q: %w", consumerGroupID, err) + + } + return true, utils.GetConsumerGroupReference(c.ConsumerGroup), nil +} + func buildKong(kongState *KongState, raw *utils.KongRawState) error { for _, s := range raw.Services { err := kongState.Services.Add(Service{Service: *s}) @@ -282,6 +294,15 @@ func buildKong(kongState *KongState, raw *utils.KongRawState) error { p.Consumer = c } } + if p.ConsumerGroup != nil && !utils.Empty(p.ConsumerGroup.ID) { + ok, cg, err := ensureConsumerGroup(kongState, *p.ConsumerGroup.ID) + if err != nil { + return err + } + if ok { + p.ConsumerGroup = cg + } + } err := kongState.Plugins.Add(Plugin{Plugin: *p}) if err != nil { return fmt.Errorf("inserting plugins into state: %w", err) diff --git a/state/consumer.go b/state/consumer.go index d2542c8ee..605311781 100644 --- a/state/consumer.go +++ b/state/consumer.go @@ -54,10 +54,23 @@ func (k *ConsumersCollection) Add(consumer Consumer) error { if !utils.Empty(consumer.Username) { searchBy = append(searchBy, *consumer.Username) } + + // search separately by id+username and by custom_id. + // + // This is because the custom_id is unique, but it may be equal to + // the username of another consumer. If we search by both id+username and + // custom_id, we may get a false positive. + _, err := getConsumer(txn, []string{"Username", "id"}, searchBy...) + if err == nil { + return fmt.Errorf("inserting consumer %v: %w", consumer.Console(), ErrAlreadyExists) + } else if !errors.Is(err, ErrNotFound) { + return err + } + if !utils.Empty(consumer.CustomID) { - searchBy = append(searchBy, *consumer.CustomID) + searchBy = []string{*consumer.CustomID} } - _, err := getConsumer(txn, searchBy...) + _, err = getConsumer(txn, []string{"CustomID"}, searchBy...) if err == nil { return fmt.Errorf("inserting consumer %v: %w", consumer.Console(), ErrAlreadyExists) } else if !errors.Is(err, ErrNotFound) { @@ -72,10 +85,9 @@ func (k *ConsumersCollection) Add(consumer Consumer) error { return nil } -func getConsumer(txn *memdb.Txn, IDs ...string) (*Consumer, error) { +func getConsumer(txn *memdb.Txn, indexes []string, IDs ...string) (*Consumer, error) { for _, id := range IDs { - res, err := multiIndexLookupUsingTxn(txn, consumerTableName, - []string{"Username", "id", "CustomID"}, id) + res, err := multiIndexLookupUsingTxn(txn, consumerTableName, indexes, id) if errors.Is(err, ErrNotFound) { continue } @@ -91,15 +103,26 @@ func getConsumer(txn *memdb.Txn, IDs ...string) (*Consumer, error) { return nil, ErrNotFound } -// Get gets a consumer by name or ID. -func (k *ConsumersCollection) Get(userNameOrID string) (*Consumer, error) { +// GetByIDOrUsername gets a consumer by name or ID. +func (k *ConsumersCollection) GetByIDOrUsername(userNameOrID string) (*Consumer, error) { if userNameOrID == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() - return getConsumer(txn, userNameOrID) + return getConsumer(txn, []string{"Username", "id"}, userNameOrID) +} + +// GetByCustomID gets a consumer by customID. +func (k *ConsumersCollection) GetByCustomID(customID string) (*Consumer, error) { + if customID == "" { + return nil, errIDRequired + } + + txn := k.db.Txn(false) + defer txn.Abort() + return getConsumer(txn, []string{"CustomID"}, customID) } // Update udpates an existing consumer. @@ -128,7 +151,7 @@ func (k *ConsumersCollection) Update(consumer Consumer) error { } func deleteConsumer(txn *memdb.Txn, userNameOrID string) error { - consumer, err := getConsumer(txn, userNameOrID) + consumer, err := getConsumer(txn, []string{"Username", "id"}, userNameOrID) if err != nil { return err } diff --git a/state/consumer_test.go b/state/consumer_test.go index 60922943e..35ecf5356 100644 --- a/state/consumer_test.go +++ b/state/consumer_test.go @@ -37,11 +37,11 @@ func TestConsumerGetUpdate(t *testing.T) { err := collection.Add(consumer) assert.Nil(err) - c, err := collection.Get("") + c, err := collection.GetByIDOrUsername("") assert.NotNil(err) assert.Nil(c) - c, err = collection.Get("first") + c, err = collection.GetByIDOrUsername("first") assert.Nil(err) assert.NotNil(c) @@ -56,11 +56,11 @@ func TestConsumerGetUpdate(t *testing.T) { c.ID = kong.String("first") assert.Nil(collection.Update(*c)) - c, err = collection.Get("my-name") + c, err = collection.GetByIDOrUsername("my-name") assert.NotNil(err) assert.Nil(c) - c, err = collection.Get("my-updated-name") + c, err = collection.GetByIDOrUsername("my-updated-name") assert.Nil(err) assert.NotNil(c) } @@ -77,12 +77,12 @@ func TestConsumerGetMemoryReference(t *testing.T) { err := collection.Add(consumer) assert.Nil(err) - c, err := collection.Get("first") + c, err := collection.GetByIDOrUsername("first") assert.Nil(err) assert.NotNil(c) c.Username = kong.String("update-should-not-reflect") - c, err = collection.Get("first") + c, err = collection.GetByIDOrUsername("first") assert.Nil(err) assert.Equal("my-name", *c.Username) } @@ -100,7 +100,7 @@ func TestConsumersInvalidType(t *testing.T) { txn.Commit() assert.Panics(func() { - collection.Get("my-name") + collection.GetByIDOrUsername("my-name") }) assert.Panics(func() { collection.GetAll() @@ -117,7 +117,7 @@ func TestConsumerDelete(t *testing.T) { err := collection.Add(consumer) assert.Nil(err) - c, err := collection.Get("my-consumer") + c, err := collection.GetByIDOrUsername("my-consumer") assert.Nil(err) assert.NotNil(c) assert.Equal("first", *c.ID) diff --git a/state/plugin.go b/state/plugin.go index 841ac1d6f..0b3951990 100644 --- a/state/plugin.go +++ b/state/plugin.go @@ -12,10 +12,11 @@ import ( var errPluginNameRequired = fmt.Errorf("name of plugin required") const ( - pluginTableName = "plugin" - pluginsByServiceID = "pluginsByServiceID" - pluginsByRouteID = "pluginsByRouteID" - pluginsByConsumerID = "pluginsByConsumerID" + pluginTableName = "plugin" + pluginsByServiceID = "pluginsByServiceID" + pluginsByRouteID = "pluginsByRouteID" + pluginsByConsumerID = "pluginsByConsumerID" + pluginsByConsumerGroupID = "pluginsByConsumerGroupID" ) var pluginTableSchema = &memdb.TableSchema{ @@ -68,6 +69,18 @@ var pluginTableSchema = &memdb.TableSchema{ }, AllowMissing: true, }, + pluginsByConsumerGroupID: { + Name: pluginsByConsumerGroupID, + Indexer: &indexers.SubFieldIndexer{ + Fields: []indexers.Field{ + { + Struct: "ConsumerGroup", + Sub: "ID", + }, + }, + }, + AllowMissing: true, + }, // combined foreign fields // FIXME bug: collision if svc/route/consumer has the same ID // and same type of plugin is created. Consider the case when only @@ -92,6 +105,10 @@ var pluginTableSchema = &memdb.TableSchema{ Struct: "Consumer", Sub: "ID", }, + { + Struct: "ConsumerGroup", + Sub: "ID", + }, }, }, }, @@ -133,7 +150,7 @@ func insertPlugin(txn *memdb.Txn, plugin Plugin) error { } // err out if another plugin with exact same combination is present - sID, rID, cID := "", "", "" + sID, rID, cID, cgID := "", "", "", "" if plugin.Service != nil && !utils.Empty(plugin.Service.ID) { sID = *plugin.Service.ID } @@ -143,7 +160,10 @@ func insertPlugin(txn *memdb.Txn, plugin Plugin) error { if plugin.Consumer != nil && !utils.Empty(plugin.Consumer.ID) { cID = *plugin.Consumer.ID } - _, err = getPluginBy(txn, *plugin.Name, sID, rID, cID) + if plugin.ConsumerGroup != nil && !utils.Empty(plugin.ConsumerGroup.ID) { + cgID = *plugin.ConsumerGroup.ID + } + _, err = getPluginBy(txn, *plugin.Name, sID, rID, cID, cgID) if err == nil { return fmt.Errorf("inserting plugin %v: %w", plugin.Console(), ErrAlreadyExists) } else if !errors.Is(err, ErrNotFound) { @@ -194,7 +214,7 @@ func (k *PluginsCollection) GetAllByName(name string) ([]*Plugin, error) { return k.getAllPluginsBy("name", name) } -func getPluginBy(txn *memdb.Txn, name, svcID, routeID, consumerID string) ( +func getPluginBy(txn *memdb.Txn, name, svcID, routeID, consumerID, consumerGroupID string) ( *Plugin, error, ) { if name == "" { @@ -202,7 +222,7 @@ func getPluginBy(txn *memdb.Txn, name, svcID, routeID, consumerID string) ( } res, err := txn.First(pluginTableName, "fields", - name, svcID, routeID, consumerID) + name, svcID, routeID, consumerID, consumerGroupID) if err != nil { return nil, err } @@ -217,18 +237,18 @@ func getPluginBy(txn *memdb.Txn, name, svcID, routeID, consumerID string) ( } // GetByProp returns a plugin which matches all the properties passed in -// the arguments. If serviceID, routeID and consumerID are empty strings, then -// a global plugin is searched. +// the arguments. If serviceID, routeID, consumerID and consumerGroupID +// are empty strings, then a global plugin is searched. // Otherwise, a plugin with name and the supplied foreign references is // searched. // name is required. -func (k *PluginsCollection) GetByProp(name, serviceID, - routeID string, consumerID string, +func (k *PluginsCollection) GetByProp( + name, serviceID, routeID, consumerID, consumerGroupID string, ) (*Plugin, error) { txn := k.db.Txn(false) defer txn.Abort() - return getPluginBy(txn, name, serviceID, routeID, consumerID) + return getPluginBy(txn, name, serviceID, routeID, consumerID, consumerGroupID) } func (k *PluginsCollection) getAllPluginsBy(index, identifier string) ( @@ -264,7 +284,7 @@ func (k *PluginsCollection) GetAllByServiceID(id string) ([]*Plugin, return k.getAllPluginsBy(pluginsByServiceID, id) } -// GetAllByRouteID returns all plugins referencing a service +// GetAllByRouteID returns all plugins referencing a route // by its id. func (k *PluginsCollection) GetAllByRouteID(id string) ([]*Plugin, error, @@ -280,6 +300,14 @@ func (k *PluginsCollection) GetAllByConsumerID(id string) ([]*Plugin, return k.getAllPluginsBy(pluginsByConsumerID, id) } +// GetAllByConsumerGroupID returns all plugins referencing a consumer-group +// by its id. +func (k *PluginsCollection) GetAllByConsumerGroupID(id string) ([]*Plugin, + error, +) { + return k.getAllPluginsBy(pluginsByConsumerGroupID, id) +} + // Update updates a plugin func (k *PluginsCollection) Update(plugin Plugin) error { // TODO abstract this check in the go-memdb library itself diff --git a/state/plugin_test.go b/state/plugin_test.go index 5ba22171b..f808806c8 100644 --- a/state/plugin_test.go +++ b/state/plugin_test.go @@ -271,9 +271,25 @@ func TestPluginsCollection_Update(t *testing.T) { }, }, } + plugin4 := Plugin{ + Plugin: kong.Plugin{ + ID: kong.String("id4"), + Name: kong.String("key-auth"), + Route: &kong.Route{ + ID: kong.String("route1"), + }, + Service: &kong.Service{ + ID: kong.String("svc1"), + }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("cg1"), + }, + }, + } k.Add(plugin1) k.Add(plugin2) k.Add(plugin3) + k.Add(plugin4) for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { @@ -362,6 +378,18 @@ func TestGetPluginByProp(t *testing.T) { }, }, }, + { + Plugin: kong.Plugin{ + ID: kong.String("5"), + Name: kong.String("key-auth"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("cg1"), + }, + Config: map[string]interface{}{ + "key5": "value5", + }, + }, + }, } assert := assert.New(t) collection := pluginsCollection() @@ -370,33 +398,38 @@ func TestGetPluginByProp(t *testing.T) { assert.Nil(collection.Add(p)) } - plugin, err := collection.GetByProp("", "", "", "") + plugin, err := collection.GetByProp("", "", "", "", "") assert.Nil(plugin) - assert.NotNil(err) + assert.Error(err) - plugin, err = collection.GetByProp("foo", "", "", "") + plugin, err = collection.GetByProp("foo", "", "", "", "") assert.Nil(plugin) assert.Equal(ErrNotFound, err) - plugin, err = collection.GetByProp("key-auth", "", "", "") - assert.Nil(err) + plugin, err = collection.GetByProp("key-auth", "", "", "", "") + assert.NoError(err) assert.NotNil(plugin) assert.Equal("value1", plugin.Config["key1"]) - plugin, err = collection.GetByProp("key-auth", "svc1", "", "") - assert.Nil(err) + plugin, err = collection.GetByProp("key-auth", "svc1", "", "", "") + assert.NoError(err) assert.NotNil(plugin) assert.Equal("value2", plugin.Config["key2"]) - plugin, err = collection.GetByProp("key-auth", "", "route1", "") - assert.Nil(err) + plugin, err = collection.GetByProp("key-auth", "", "route1", "", "") + assert.NoError(err) assert.NotNil(plugin) assert.Equal("value3", plugin.Config["key3"]) - plugin, err = collection.GetByProp("key-auth", "", "", "consumer1") - assert.Nil(err) + plugin, err = collection.GetByProp("key-auth", "", "", "consumer1", "") + assert.NoError(err) assert.NotNil(plugin) assert.Equal("value4", plugin.Config["key4"]) + + plugin, err = collection.GetByProp("key-auth", "", "", "", "cg1") + assert.NoError(err) + assert.NotNil(plugin) + assert.Equal("value5", plugin.Config["key5"]) } func TestPluginsInvalidType(t *testing.T) { diff --git a/state/types.go b/state/types.go index 88dcb5795..b9f14fb3c 100644 --- a/state/types.go +++ b/state/types.go @@ -98,6 +98,13 @@ func (s1 *Service) EqualWithOpts(s2 *Service, s1Copy := s1.Service.DeepCopy() s2Copy := s2.Service.DeepCopy() + if len(s1Copy.Tags) == 0 { + s1Copy.Tags = nil + } + if len(s2Copy.Tags) == 0 { + s2Copy.Tags = nil + } + // Cassandra can sometimes mess up tag order, but tag order doesn't actually matter: tags are sets // even though we represent them with slices. Sort before comparison to avoid spurious diff detection. sort.Slice(s1Copy.Tags, func(i, j int) bool { return *(s1Copy.Tags[i]) < *(s1Copy.Tags[j]) }) @@ -153,6 +160,13 @@ func (r1 *Route) EqualWithOpts(r2 *Route, ignoreID, r1Copy := r1.Route.DeepCopy() r2Copy := r2.Route.DeepCopy() + if len(r1Copy.Tags) == 0 { + r1Copy.Tags = nil + } + if len(r2Copy.Tags) == 0 { + r2Copy.Tags = nil + } + sort.Slice(r1Copy.Tags, func(i, j int) bool { return *(r1Copy.Tags[i]) < *(r1Copy.Tags[j]) }) sort.Slice(r2Copy.Tags, func(i, j int) bool { return *(r2Copy.Tags[i]) < *(r2Copy.Tags[j]) }) @@ -216,6 +230,13 @@ func (u1 *Upstream) EqualWithOpts(u2 *Upstream, u1Copy := u1.Upstream.DeepCopy() u2Copy := u2.Upstream.DeepCopy() + if len(u1Copy.Tags) == 0 { + u1Copy.Tags = nil + } + if len(u2Copy.Tags) == 0 { + u2Copy.Tags = nil + } + sort.Slice(u1Copy.Tags, func(i, j int) bool { return *(u1Copy.Tags[i]) < *(u1Copy.Tags[j]) }) sort.Slice(u2Copy.Tags, func(i, j int) bool { return *(u2Copy.Tags[i]) < *(u2Copy.Tags[j]) }) @@ -270,6 +291,13 @@ func (t1 *Target) EqualWithOpts(t2 *Target, ignoreID, t1Copy := t1.Target.DeepCopy() t2Copy := t2.Target.DeepCopy() + if len(t1Copy.Tags) == 0 { + t1Copy.Tags = nil + } + if len(t2Copy.Tags) == 0 { + t2Copy.Tags = nil + } + sort.Slice(t1Copy.Tags, func(i, j int) bool { return *(t1Copy.Tags[i]) < *(t1Copy.Tags[j]) }) sort.Slice(t2Copy.Tags, func(i, j int) bool { return *(t2Copy.Tags[i]) < *(t2Copy.Tags[j]) }) @@ -324,6 +352,13 @@ func (c1 *Certificate) EqualWithOpts(c2 *Certificate, c1Copy := c1.Certificate.DeepCopy() c2Copy := c2.Certificate.DeepCopy() + if len(c1Copy.Tags) == 0 { + c1Copy.Tags = nil + } + if len(c2Copy.Tags) == 0 { + c2Copy.Tags = nil + } + sort.Slice(c1Copy.Tags, func(i, j int) bool { return *(c1Copy.Tags[i]) < *(c1Copy.Tags[j]) }) sort.Slice(c2Copy.Tags, func(i, j int) bool { return *(c2Copy.Tags[i]) < *(c2Copy.Tags[j]) }) @@ -374,6 +409,13 @@ func (s1 *SNI) EqualWithOpts(s2 *SNI, ignoreID, s1Copy := s1.SNI.DeepCopy() s2Copy := s2.SNI.DeepCopy() + if len(s1Copy.Tags) == 0 { + s1Copy.Tags = nil + } + if len(s2Copy.Tags) == 0 { + s2Copy.Tags = nil + } + sort.Slice(s1Copy.Tags, func(i, j int) bool { return *(s1Copy.Tags[i]) < *(s1Copy.Tags[j]) }) sort.Slice(s2Copy.Tags, func(i, j int) bool { return *(s2Copy.Tags[i]) < *(s2Copy.Tags[j]) }) @@ -425,6 +467,9 @@ func (p1 *Plugin) Console() string { if p1.Consumer != nil { associations = append(associations, "consumer "+p1.Consumer.FriendlyName()) } + if p1.ConsumerGroup != nil { + associations = append(associations, "consumer-group "+p1.ConsumerGroup.FriendlyName()) + } if len(associations) > 0 { res += "for " } @@ -452,6 +497,13 @@ func (p1 *Plugin) EqualWithOpts(p2 *Plugin, ignoreID, p1Copy := p1.Plugin.DeepCopy() p2Copy := p2.Plugin.DeepCopy() + if len(p1Copy.Tags) == 0 { + p1Copy.Tags = nil + } + if len(p2Copy.Tags) == 0 { + p2Copy.Tags = nil + } + sort.Slice(p1Copy.Tags, func(i, j int) bool { return *(p1Copy.Tags[i]) < *(p1Copy.Tags[j]) }) sort.Slice(p2Copy.Tags, func(i, j int) bool { return *(p2Copy.Tags[i]) < *(p2Copy.Tags[j]) }) @@ -470,6 +522,7 @@ func (p1 *Plugin) EqualWithOpts(p2 *Plugin, ignoreID, p2Copy.Service = nil p2Copy.Route = nil p2Copy.Consumer = nil + p2Copy.ConsumerGroup = nil } if p1Copy.Service != nil { @@ -490,6 +543,12 @@ func (p1 *Plugin) EqualWithOpts(p2 *Plugin, ignoreID, if p2Copy.Consumer != nil { p2Copy.Consumer.Username = nil } + if p1Copy.ConsumerGroup != nil { + p1Copy.ConsumerGroup.Name = nil + } + if p2Copy.ConsumerGroup != nil { + p2Copy.ConsumerGroup.Name = nil + } return reflect.DeepEqual(p1Copy, p2Copy) } @@ -528,6 +587,13 @@ func (c1 *Consumer) EqualWithOpts(c2 *Consumer, c1Copy := c1.Consumer.DeepCopy() c2Copy := c2.Consumer.DeepCopy() + if len(c1Copy.Tags) == 0 { + c1Copy.Tags = nil + } + if len(c2Copy.Tags) == 0 { + c2Copy.Tags = nil + } + sort.Slice(c1Copy.Tags, func(i, j int) bool { return *(c1Copy.Tags[i]) < *(c1Copy.Tags[j]) }) sort.Slice(c2Copy.Tags, func(i, j int) bool { return *(c2Copy.Tags[i]) < *(c2Copy.Tags[j]) }) @@ -778,6 +844,13 @@ func (k1 *KeyAuth) EqualWithOpts(k2 *KeyAuth, ignoreID, k1Copy := k1.KeyAuth.DeepCopy() k2Copy := k2.KeyAuth.DeepCopy() + if len(k1Copy.Tags) == 0 { + k1Copy.Tags = nil + } + if len(k2Copy.Tags) == 0 { + k2Copy.Tags = nil + } + sort.Slice(k1Copy.Tags, func(i, j int) bool { return *(k1Copy.Tags[i]) < *(k1Copy.Tags[j]) }) sort.Slice(k2Copy.Tags, func(i, j int) bool { return *(k2Copy.Tags[i]) < *(k2Copy.Tags[j]) }) @@ -856,6 +929,13 @@ func (h1 *HMACAuth) EqualWithOpts(h2 *HMACAuth, ignoreID, h1Copy := h1.HMACAuth.DeepCopy() h2Copy := h2.HMACAuth.DeepCopy() + if len(h1Copy.Tags) == 0 { + h1Copy.Tags = nil + } + if len(h2Copy.Tags) == 0 { + h2Copy.Tags = nil + } + sort.Slice(h1Copy.Tags, func(i, j int) bool { return *(h1Copy.Tags[i]) < *(h1Copy.Tags[j]) }) sort.Slice(h2Copy.Tags, func(i, j int) bool { return *(h2Copy.Tags[i]) < *(h2Copy.Tags[j]) }) @@ -934,6 +1014,13 @@ func (j1 *JWTAuth) EqualWithOpts(j2 *JWTAuth, ignoreID, j1Copy := j1.JWTAuth.DeepCopy() j2Copy := j2.JWTAuth.DeepCopy() + if len(j1Copy.Tags) == 0 { + j1Copy.Tags = nil + } + if len(j2Copy.Tags) == 0 { + j2Copy.Tags = nil + } + sort.Slice(j1Copy.Tags, func(i, j int) bool { return *(j1Copy.Tags[i]) < *(j1Copy.Tags[j]) }) sort.Slice(j2Copy.Tags, func(i, j int) bool { return *(j2Copy.Tags[i]) < *(j2Copy.Tags[j]) }) @@ -1012,6 +1099,13 @@ func (b1 *BasicAuth) EqualWithOpts(b2 *BasicAuth, ignoreID, b1Copy := b1.BasicAuth.DeepCopy() b2Copy := b2.BasicAuth.DeepCopy() + if len(b1Copy.Tags) == 0 { + b1Copy.Tags = nil + } + if len(b2Copy.Tags) == 0 { + b2Copy.Tags = nil + } + sort.Slice(b1Copy.Tags, func(i, j int) bool { return *(b1Copy.Tags[i]) < *(b1Copy.Tags[j]) }) sort.Slice(b2Copy.Tags, func(i, j int) bool { return *(b2Copy.Tags[i]) < *(b2Copy.Tags[j]) }) @@ -1094,6 +1188,13 @@ func (b1 *ACLGroup) EqualWithOpts(b2 *ACLGroup, ignoreID, b1Copy := b1.ACLGroup.DeepCopy() b2Copy := b2.ACLGroup.DeepCopy() + if len(b1Copy.Tags) == 0 { + b1Copy.Tags = nil + } + if len(b2Copy.Tags) == 0 { + b2Copy.Tags = nil + } + sort.Slice(b1Copy.Tags, func(i, j int) bool { return *(b1Copy.Tags[i]) < *(b1Copy.Tags[j]) }) sort.Slice(b2Copy.Tags, func(i, j int) bool { return *(b2Copy.Tags[i]) < *(b2Copy.Tags[j]) }) @@ -1154,6 +1255,13 @@ func (c1 *CACertificate) EqualWithOpts(c2 *CACertificate, c1Copy := c1.CACertificate.DeepCopy() c2Copy := c2.CACertificate.DeepCopy() + if len(c1Copy.Tags) == 0 { + c1Copy.Tags = nil + } + if len(c2Copy.Tags) == 0 { + c2Copy.Tags = nil + } + sort.Slice(c1Copy.Tags, func(i, j int) bool { return *(c1Copy.Tags[i]) < *(c1Copy.Tags[j]) }) sort.Slice(c2Copy.Tags, func(i, j int) bool { return *(c2Copy.Tags[i]) < *(c2Copy.Tags[j]) }) @@ -1195,6 +1303,13 @@ func (k1 *Oauth2Credential) EqualWithOpts(k2 *Oauth2Credential, ignoreID, k1Copy := k1.Oauth2Credential.DeepCopy() k2Copy := k2.Oauth2Credential.DeepCopy() + if len(k1Copy.Tags) == 0 { + k1Copy.Tags = nil + } + if len(k2Copy.Tags) == 0 { + k2Copy.Tags = nil + } + sort.Slice(k1Copy.Tags, func(i, j int) bool { return *(k1Copy.Tags[i]) < *(k1Copy.Tags[j]) }) sort.Slice(k2Copy.Tags, func(i, j int) bool { return *(k2Copy.Tags[i]) < *(k2Copy.Tags[j]) }) @@ -1273,6 +1388,13 @@ func (b1 *MTLSAuth) EqualWithOpts(b2 *MTLSAuth, ignoreID, b1Copy := b1.MTLSAuth.DeepCopy() b2Copy := b2.MTLSAuth.DeepCopy() + if len(b1Copy.Tags) == 0 { + b1Copy.Tags = nil + } + if len(b2Copy.Tags) == 0 { + b2Copy.Tags = nil + } + sort.Slice(b1Copy.Tags, func(i, j int) bool { return *(b1Copy.Tags[i]) < *(b1Copy.Tags[j]) }) sort.Slice(b2Copy.Tags, func(i, j int) bool { return *(b2Copy.Tags[i]) < *(b2Copy.Tags[j]) }) @@ -1454,6 +1576,13 @@ func (v1 *Vault) EqualWithOpts(v2 *Vault, ignoreID, ignoreTS bool) bool { v1Copy := v1.Vault.DeepCopy() v2Copy := v2.Vault.DeepCopy() + if len(v1Copy.Tags) == 0 { + v1Copy.Tags = nil + } + if len(v2Copy.Tags) == 0 { + v2Copy.Tags = nil + } + sort.Slice(v1Copy.Tags, func(i, j int) bool { return *(v1Copy.Tags[i]) < *(v1Copy.Tags[j]) }) sort.Slice(v2Copy.Tags, func(i, j int) bool { return *(v2Copy.Tags[i]) < *(v2Copy.Tags[j]) }) diff --git a/tests/integration/diff_test.go b/tests/integration/diff_test.go index 1f9f94cf9..b9d3a7184 100644 --- a/tests/integration/diff_test.go +++ b/tests/integration/diff_test.go @@ -67,6 +67,394 @@ Summary: "DECK_FUB": "fubfub", // unused "DECK_FOO": "foo_test", // unused, partial match } + + expectedOutputUnMaskedJSON = `{ + "changes": { + "creating": [ + { + "name": "rate-limiting (global)", + "kind": "plugin", + "body": { + "new": { + "id": "a1368a28-cb5c-4eee-86d8-03a6bdf94b5e", + "name": "rate-limiting", + "config": { + "day": null, + "error_code": 429, + "error_message": "API rate limit exceeded", + "fault_tolerant": true, + "header_name": null, + "hide_client_headers": false, + "hour": null, + "limit_by": "consumer", + "minute": 123, + "month": null, + "path": null, + "policy": "local", + "redis_database": 0, + "redis_host": null, + "redis_password": null, + "redis_port": 6379, + "redis_server_name": null, + "redis_ssl": false, + "redis_ssl_verify": false, + "redis_timeout": 2000, + "redis_username": null, + "second": null, + "year": null + }, + "enabled": true, + "protocols": [ + "grpc", + "grpcs", + "http", + "https" + ] + }, + "old": null + } + } + ], + "updating": [ + { + "name": "svc1", + "kind": "service", + "body": { + "new": { + "connect_timeout": 60000, + "enabled": true, + "host": "mockbin.org", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000, + "tags": [ + "test" + ] + }, + "old": { + "connect_timeout": 60000, + "enabled": true, + "host": "mockbin.org", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000 + } + } + } + ], + "deleting": [] + }, + "summary": { + "creating": 1, + "updating": 1, + "deleting": 0, + "total": 2 + }, + "warnings": [], + "errors": [] +} + +` + + expectedOutputMaskedJSON = `{ + "changes": { + "creating": [ + { + "name": "rate-limiting (global)", + "kind": "plugin", + "body": { + "new": { + "id": "a1368a28-cb5c-4eee-86d8-03a6bdf94b5e", + "name": "rate-limiting", + "config": { + "day": null, + "error_code": 429, + "error_message": "API rate limit exceeded", + "fault_tolerant": true, + "header_name": null, + "hide_client_headers": false, + "hour": null, + "limit_by": "consumer", + "minute": 123, + "month": null, + "path": null, + "policy": "local", + "redis_database": 0, + "redis_host": null, + "redis_password": null, + "redis_port": 6379, + "redis_server_name": null, + "redis_ssl": false, + "redis_ssl_verify": false, + "redis_timeout": 2000, + "redis_username": null, + "second": null, + "year": null + }, + "enabled": true, + "protocols": [ + "grpc", + "grpcs", + "http", + "https" + ] + }, + "old": null + } + } + ], + "updating": [ + { + "name": "svc1", + "kind": "service", + "body": { + "new": { + "connect_timeout": 60000, + "enabled": true, + "host": "[masked]", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000, + "tags": [ + "[masked] is an external host. I like [masked]!", + "foo:foo", + "baz:[masked]", + "another:[masked]", + "bar:[masked]" + ] + }, + "old": { + "connect_timeout": 60000, + "enabled": true, + "host": "[masked]", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000 + } + } + } + ], + "deleting": [] + }, + "summary": { + "creating": 1, + "updating": 1, + "deleting": 0, + "total": 2 + }, + "warnings": [], + "errors": [] +} + +` + + expectedOutputUnMaskedJSON30x = `{ + "changes": { + "creating": [ + { + "name": "rate-limiting (global)", + "kind": "plugin", + "body": { + "new": { + "id": "a1368a28-cb5c-4eee-86d8-03a6bdf94b5e", + "name": "rate-limiting", + "config": { + "day": null, + "fault_tolerant": true, + "header_name": null, + "hide_client_headers": false, + "hour": null, + "limit_by": "consumer", + "minute": 123, + "month": null, + "path": null, + "policy": "local", + "redis_database": 0, + "redis_host": null, + "redis_password": null, + "redis_port": 6379, + "redis_server_name": null, + "redis_ssl": false, + "redis_ssl_verify": false, + "redis_timeout": 2000, + "redis_username": null, + "second": null, + "year": null + }, + "enabled": true, + "protocols": [ + "grpc", + "grpcs", + "http", + "https" + ] + }, + "old": null + } + } + ], + "updating": [ + { + "name": "svc1", + "kind": "service", + "body": { + "new": { + "connect_timeout": 60000, + "enabled": true, + "host": "mockbin.org", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000, + "tags": [ + "test" + ] + }, + "old": { + "connect_timeout": 60000, + "enabled": true, + "host": "mockbin.org", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000 + } + } + } + ], + "deleting": [] + }, + "summary": { + "creating": 1, + "updating": 1, + "deleting": 0, + "total": 2 + }, + "warnings": [], + "errors": [] +} + +` + + expectedOutputMaskedJSON30x = `{ + "changes": { + "creating": [ + { + "name": "rate-limiting (global)", + "kind": "plugin", + "body": { + "new": { + "id": "a1368a28-cb5c-4eee-86d8-03a6bdf94b5e", + "name": "rate-limiting", + "config": { + "day": null, + "fault_tolerant": true, + "header_name": null, + "hide_client_headers": false, + "hour": null, + "limit_by": "consumer", + "minute": 123, + "month": null, + "path": null, + "policy": "local", + "redis_database": 0, + "redis_host": null, + "redis_password": null, + "redis_port": 6379, + "redis_server_name": null, + "redis_ssl": false, + "redis_ssl_verify": false, + "redis_timeout": 2000, + "redis_username": null, + "second": null, + "year": null + }, + "enabled": true, + "protocols": [ + "grpc", + "grpcs", + "http", + "https" + ] + }, + "old": null + } + } + ], + "updating": [ + { + "name": "svc1", + "kind": "service", + "body": { + "new": { + "connect_timeout": 60000, + "enabled": true, + "host": "[masked]", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000, + "tags": [ + "[masked] is an external host. I like [masked]!", + "foo:foo", + "baz:[masked]", + "another:[masked]", + "bar:[masked]" + ] + }, + "old": { + "connect_timeout": 60000, + "enabled": true, + "host": "[masked]", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000 + } + } + } + ], + "deleting": [] + }, + "summary": { + "creating": 1, + "updating": 1, + "deleting": 0, + "total": 2 + }, + "warnings": [], + "errors": [] +} + +` ) // test scope: @@ -86,8 +474,7 @@ func Test_Diff_Workspace_OlderThan3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "<3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) _, err := diff(tc.stateFile) assert.NoError(t, err) @@ -111,8 +498,7 @@ func Test_Diff_Workspace_NewerThan3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) _, err := diff(tc.stateFile) assert.NoError(t, err) @@ -143,8 +529,7 @@ func Test_Diff_Masked_OlderThan3x(t *testing.T) { t.Setenv(k, v) } runWhen(t, "kong", "==2.8.0") - teardown := setup(t) - defer teardown(t) + setup(t) // initialize state assert.NoError(t, sync(tc.initialStateFile)) @@ -154,6 +539,23 @@ func Test_Diff_Masked_OlderThan3x(t *testing.T) { assert.Equal(t, expectedOutputMasked, out) }) } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", "==2.8.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputMaskedJSON, out) + }) + } } // test scope: @@ -179,8 +581,7 @@ func Test_Diff_Masked_NewerThan3x(t *testing.T) { t.Setenv(k, v) } runWhen(t, "kong", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // initialize state assert.NoError(t, sync(tc.initialStateFile)) @@ -190,11 +591,43 @@ func Test_Diff_Masked_NewerThan3x(t *testing.T) { assert.Equal(t, expectedOutputMasked, out) }) } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", ">=3.0.0 <3.1.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputMaskedJSON30x, out) + }) + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", ">=3.1.0 <3.4.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputMaskedJSON, out) + }) + } } // test scope: // - 2.8.0 -func Test_Diff_Unasked_OlderThan3x(t *testing.T) { +func Test_Diff_Unmasked_OlderThan3x(t *testing.T) { tests := []struct { name string initialStateFile string @@ -215,8 +648,7 @@ func Test_Diff_Unasked_OlderThan3x(t *testing.T) { t.Setenv(k, v) } runWhen(t, "kong", "==2.8.0") - teardown := setup(t) - defer teardown(t) + setup(t) // initialize state assert.NoError(t, sync(tc.initialStateFile)) @@ -226,11 +658,27 @@ func Test_Diff_Unasked_OlderThan3x(t *testing.T) { assert.Equal(t, expectedOutputUnMasked, out) }) } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", "==2.8.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--no-mask-deck-env-vars-value", "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputUnMaskedJSON, out) + }) + } } // test scope: // - 3.x -func Test_Diff_Unasked_NewerThan3x(t *testing.T) { +func Test_Diff_Unmasked_NewerThan3x(t *testing.T) { tests := []struct { name string initialStateFile string @@ -251,8 +699,7 @@ func Test_Diff_Unasked_NewerThan3x(t *testing.T) { t.Setenv(k, v) } runWhen(t, "kong", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // initialize state assert.NoError(t, sync(tc.initialStateFile)) @@ -262,4 +709,36 @@ func Test_Diff_Unasked_NewerThan3x(t *testing.T) { assert.Equal(t, expectedOutputUnMasked, out) }) } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", ">=3.0.0 <3.1.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--no-mask-deck-env-vars-value", "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputUnMaskedJSON30x, out) + }) + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", ">=3.1.0 <3.4.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--no-mask-deck-env-vars-value", "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputUnMaskedJSON, out) + }) + } } diff --git a/tests/integration/dump_test.go b/tests/integration/dump_test.go index 015da2835..bda8df6a0 100644 --- a/tests/integration/dump_test.go +++ b/tests/integration/dump_test.go @@ -23,8 +23,7 @@ func Test_Dump_SelectTags_30(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.0.0 <3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) assert.NoError(t, sync(tc.stateFile)) @@ -57,8 +56,7 @@ func Test_Dump_SelectTags_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) assert.NoError(t, sync(tc.stateFile)) @@ -82,25 +80,55 @@ func Test_Dump_SkipConsumers(t *testing.T) { stateFile string expectedFile string skipConsumers bool + runWhen func(t *testing.T) }{ { - name: "dump with skip-consumers", + name: "3.2 & 3.3 dump with skip-consumers", stateFile: "testdata/dump/002-skip-consumers/kong.yaml", expectedFile: "testdata/dump/002-skip-consumers/expected.yaml", skipConsumers: true, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.2.0 <3.4.0") }, }, { - name: "dump with no skip-consumers", + name: "3.2 & 3.3 dump with no skip-consumers", stateFile: "testdata/dump/002-skip-consumers/kong.yaml", expectedFile: "testdata/dump/002-skip-consumers/expected-no-skip.yaml", skipConsumers: false, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.2.0 <3.4.0") }, + }, + { + name: "3.4 dump with skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected.yaml", + skipConsumers: true, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.4.0 <3.5.0") }, + }, + { + name: "3.4 dump with no skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected-no-skip-34.yaml", + skipConsumers: false, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.4.0 <3.5.0") }, + }, + { + name: "3.5 dump with skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected.yaml", + skipConsumers: true, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.5.0") }, + }, + { + name: "3.5 dump with no skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected-no-skip-35.yaml", + skipConsumers: false, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.5.0") }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "enterprise", ">=3.2.0") - teardown := setup(t) - defer teardown(t) + tc.runWhen(t) + setup(t) assert.NoError(t, sync(tc.stateFile)) @@ -122,7 +150,57 @@ func Test_Dump_SkipConsumers(t *testing.T) { expected, err := readFile(tc.expectedFile) assert.NoError(t, err) - assert.Equal(t, output, expected) + assert.Equal(t, expected, output) + }) + } +} + +func Test_Dump_SkipConsumers_Konnect(t *testing.T) { + tests := []struct { + name string + stateFile string + expectedFile string + skipConsumers bool + }{ + { + name: "dump with skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected_konnect.yaml", + skipConsumers: true, + }, + { + name: "dump with no skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected-no-skip_konnect.yaml", + skipConsumers: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhenKonnect(t) + setup(t) + + assert.NoError(t, sync(tc.stateFile)) + + var ( + output string + err error + ) + if tc.skipConsumers { + output, err = dump( + "--skip-consumers", + "-o", "-", + ) + } else { + output, err = dump( + "-o", "-", + ) + } + assert.NoError(t, err) + + expected, err := readFile(tc.expectedFile) + assert.NoError(t, err) + assert.Equal(t, expected, output) }) } } diff --git a/tests/integration/ping_test.go b/tests/integration/ping_test.go index 748f8d01c..49561e76c 100644 --- a/tests/integration/ping_test.go +++ b/tests/integration/ping_test.go @@ -3,14 +3,24 @@ package integration import ( + "os" "testing" "github.com/stretchr/testify/require" ) func Test_KonnectPing(t *testing.T) { - t.Run("konnect ping", func(t *testing.T) { + t.Run("konnect ping - email/password", func(t *testing.T) { runWhen(t, "konnect", "") - require.NoError(t, ping()) + require.NoError(t, ping( + "--konnect-email", os.Getenv("DECK_KONNECT_EMAIL"), + "--konnect-password", os.Getenv("DECK_KONNECT_PASSWORD"), + )) + }) + t.Run("konnect ping - token", func(t *testing.T) { + runWhen(t, "konnect", "") + require.NoError(t, ping( + "--konnect-token", os.Getenv("DECK_KONNECT_TOKEN"), + )) }) } diff --git a/tests/integration/reset_test.go b/tests/integration/reset_test.go index aea8c56f3..28ed397bd 100644 --- a/tests/integration/reset_test.go +++ b/tests/integration/reset_test.go @@ -62,8 +62,7 @@ func Test_Reset_SkipCACert_2x(t *testing.T) { // here because the schema changed and the entities aren't the same // across all versions, even though the skip functionality works the same. runWhen(t, "kong", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) reset(t, "--skip-ca-certificates") @@ -99,8 +98,7 @@ func Test_Reset_SkipCACert_3x(t *testing.T) { // here because the schema changed and the entities aren't the same // across all versions, even though the skip functionality works the same. runWhen(t, "kong", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) reset(t, "--skip-ca-certificates") diff --git a/tests/integration/sync_test.go b/tests/integration/sync_test.go index 598266d7e..5e425a7f0 100644 --- a/tests/integration/sync_test.go +++ b/tests/integration/sync_test.go @@ -6,6 +6,7 @@ import ( "crypto/tls" "crypto/x509" "errors" + "fmt" "net/http" "testing" "time" @@ -627,10 +628,10 @@ var ( { Name: kong.String("rate-limiting-advanced"), Config: kong.Configuration{ - "limit": []*int32{int32p(7)}, - "retry_after_jitter_max": int32p(1), - "window_size": []*int32{int32p(60)}, - "window_type": kong.String("sliding"), + "limit": []any{float64(7)}, + "retry_after_jitter_max": float64(1), + "window_size": []any{float64(60)}, + "window_type": "sliding", }, ConsumerGroup: &kong.ConsumerGroup{ ID: kong.String("521a90ad-36cb-4e31-a5db-1d979aee40d1"), @@ -652,10 +653,10 @@ var ( { Name: kong.String("rate-limiting-advanced"), Config: kong.Configuration{ - "limit": []*int32{int32p(10)}, - "retry_after_jitter_max": int32p(1), - "window_size": []*int32{int32p(60)}, - "window_type": kong.String("sliding"), + "limit": []any{float64(10)}, + "retry_after_jitter_max": float64(1), + "window_size": []any{float64(60)}, + "window_type": "sliding", }, ConsumerGroup: &kong.ConsumerGroup{ ID: kong.String("92177268-b134-42f9-909a-36f9d2d3d5e7"), @@ -821,157 +822,343 @@ var ( Protocols: []*string{kong.String("http"), kong.String("https")}, }, } -) - -// test scope: -// - 1.4.3 -func Test_Sync_ServicesRoutes_Till_1_4_3(t *testing.T) { - // setup stage - client, err := getTestClient() - if err != nil { - t.Errorf(err.Error()) - } - - // ignore entities fields based on Kong version - ignoreFields := []cmp.Option{ - cmpopts.IgnoreFields(kong.Route{}, "Service"), - } - tests := []struct { - name string - kongFile string - expectedState utils.KongRawState - }{ + consumerGroupScopedPlugins = []*kong.Plugin{ { - name: "creates a service", - kongFile: "testdata/sync/001-create-a-service/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("77e6691d-67c0-446a-9401-27be2b141aae"), }, - }, - { - name: "create services and routes", - kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, - Routes: route1_143, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(10)}, + "namespace": string("gold"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", "<=1.4.3") - teardown := setup(t) - defer teardown(t) - - sync(tc.kongFile) - testKongState(t, client, false, tc.expectedState, ignoreFields) - }) - } -} - -// test scope: -// - 1.5.1 -// - 1.5.0.11+enterprise -func Test_Sync_ServicesRoutes_Till_1_5_1(t *testing.T) { - // setup stage - client, err := getTestClient() - if err != nil { - t.Errorf(err.Error()) - } - - tests := []struct { - name string - kongFile string - expectedState utils.KongRawState - }{ { - name: "creates a service", - kongFile: "testdata/sync/001-create-a-service/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("5bcbd3a7-030b-4310-bd1d-2721ff85d236"), }, - }, - { - name: "create services and routes", - kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, - Routes: route1_151, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(7)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", ">1.4.3 <=1.5.1") - teardown := setup(t) - defer teardown(t) - - sync(tc.kongFile) - testKongState(t, client, false, tc.expectedState, nil) - }) - } -} - -// test scope: -// - 2.0.5 -// - 2.1.4 -func Test_Sync_ServicesRoutes_From_2_0_5_To_2_1_4(t *testing.T) { - // setup stage - client, err := getTestClient() - if err != nil { - t.Errorf(err.Error()) - } - - tests := []struct { - name string - kongFile string - expectedState utils.KongRawState - }{ { - name: "creates a service", - kongFile: "testdata/sync/001-create-a-service/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, + Name: kong.String("rate-limiting-advanced"), + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(5)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, }, { - name: "create services and routes", - kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, - Routes: route1_205_214, + Name: kong.String("key-auth"), + Config: kong.Configuration{ + "anonymous": nil, + "hide_credentials": false, + "key_in_body": false, + "key_in_header": true, + "key_in_query": true, + "key_names": []interface{}{"apikey"}, + "run_on_preflight": true, }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("http"), kong.String("https")}, }, } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", ">=2.0.5 <=2.1.4") - teardown := setup(t) - defer teardown(t) - - sync(tc.kongFile) - testKongState(t, client, false, tc.expectedState, nil) - }) - } -} -// test scope: -// - 2.2.2 -// - 2.3.3 -// - 2.4.1 -// - 2.5.1 -// - 2.6.0 -// - 2.2.1.3+enterprise -// - 2.3.3.4+enterprise -// - 2.4.1.3+enterprise -// - 2.5.1.2+enterprise -func Test_Sync_ServicesRoutes_From_2_2_1_to_2_6_0(t *testing.T) { - // setup stage - client, err := getTestClient() - if err != nil { - t.Errorf(err.Error()) - } + consumerGroupScopedPlugins35x = []*kong.Plugin{ + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("77e6691d-67c0-446a-9401-27be2b141aae"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(10)}, + "namespace": string("gold"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(256), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("5bcbd3a7-030b-4310-bd1d-2721ff85d236"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(7)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(256), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + { + Name: kong.String("rate-limiting-advanced"), + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(5)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(256), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + { + Name: kong.String("key-auth"), + Config: kong.Configuration{ + "anonymous": nil, + "hide_credentials": false, + "key_in_body": false, + "key_in_header": true, + "key_in_query": true, + "key_names": []interface{}{"apikey"}, + "run_on_preflight": true, + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("http"), kong.String("https")}, + }, + } +) + +// test scope: +// - 1.4.3 +func Test_Sync_ServicesRoutes_Till_1_4_3(t *testing.T) { + // setup stage + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + // ignore entities fields based on Kong version + ignoreFields := []cmp.Option{ + cmpopts.IgnoreFields(kong.Route{}, "Service"), + } tests := []struct { name string @@ -990,28 +1177,25 @@ func Test_Sync_ServicesRoutes_From_2_2_1_to_2_6_0(t *testing.T) { kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", expectedState: utils.KongRawState{ Services: svc1, - Routes: route1_20x, + Routes: route1_143, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", ">2.2.1 <=2.6.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "kong", "<=1.4.3") + setup(t) sync(tc.kongFile) - testKongState(t, client, false, tc.expectedState, nil) + testKongState(t, client, false, tc.expectedState, ignoreFields) }) } } // test scope: -// - 2.7.0 -// - 2.6.0.2+enterprise -// - 2.7.0.0+enterprise -// - 2.8.0.0+enterprise -func Test_Sync_ServicesRoutes_From_2_6_9_Till_2_8_0(t *testing.T) { +// - 1.5.1 +// - 1.5.0.11+enterprise +func Test_Sync_ServicesRoutes_Till_1_5_1(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1027,23 +1211,22 @@ func Test_Sync_ServicesRoutes_From_2_6_9_Till_2_8_0(t *testing.T) { name: "creates a service", kongFile: "testdata/sync/001-create-a-service/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, + Services: svc1, }, }, { name: "create services and routes", kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, - Routes: route1_20x, + Services: svc1, + Routes: route1_151, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", ">2.6.9 <3.0.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "kong", ">1.4.3 <=1.5.1") + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1052,8 +1235,9 @@ func Test_Sync_ServicesRoutes_From_2_6_9_Till_2_8_0(t *testing.T) { } // test scope: -// - 3.x -func Test_Sync_ServicesRoutes_From_3x(t *testing.T) { +// - 2.0.5 +// - 2.1.4 +func Test_Sync_ServicesRoutes_From_2_0_5_To_2_1_4(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1067,25 +1251,24 @@ func Test_Sync_ServicesRoutes_From_3x(t *testing.T) { }{ { name: "creates a service", - kongFile: "testdata/sync/001-create-a-service/kong3x.yaml", + kongFile: "testdata/sync/001-create-a-service/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, + Services: svc1, }, }, { name: "create services and routes", - kongFile: "testdata/sync/002-create-services-and-routes/kong3x.yaml", + kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, - Routes: route1_20x, + Services: svc1, + Routes: route1_205_214, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "kong", ">=2.0.5 <=2.1.4") + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1094,8 +1277,16 @@ func Test_Sync_ServicesRoutes_From_3x(t *testing.T) { } // test scope: -// - konnect -func Test_Sync_ServicesRoutes_Konnect(t *testing.T) { +// - 2.2.2 +// - 2.3.3 +// - 2.4.1 +// - 2.5.1 +// - 2.6.0 +// - 2.2.1.3+enterprise +// - 2.3.3.4+enterprise +// - 2.4.1.3+enterprise +// - 2.5.1.2+enterprise +func Test_Sync_ServicesRoutes_From_2_2_1_to_2_6_0(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1109,25 +1300,24 @@ func Test_Sync_ServicesRoutes_Konnect(t *testing.T) { }{ { name: "creates a service", - kongFile: "testdata/sync/001-create-a-service/kong3x.yaml", + kongFile: "testdata/sync/001-create-a-service/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, + Services: svc1, }, }, { name: "create services and routes", - kongFile: "testdata/sync/002-create-services-and-routes/kong3x.yaml", + kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, + Services: svc1, Routes: route1_20x, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "konnect", "") - teardown := setup(t) - defer teardown(t) + runWhen(t, "kong", ">2.2.1 <=2.6.0") + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1136,8 +1326,11 @@ func Test_Sync_ServicesRoutes_Konnect(t *testing.T) { } // test scope: -// - 1.4.3 -func Test_Sync_BasicAuth_Plugin_1_4_3(t *testing.T) { +// - 2.7.0 +// - 2.6.0.2+enterprise +// - 2.7.0.0+enterprise +// - 2.8.0.0+enterprise +func Test_Sync_ServicesRoutes_From_2_6_9_Till_2_8_0(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1145,24 +1338,30 @@ func Test_Sync_BasicAuth_Plugin_1_4_3(t *testing.T) { } tests := []struct { - name string - kongFile string - initialKongFile string - expectedState utils.KongRawState + name string + kongFile string + expectedState utils.KongRawState }{ { - name: "create a plugin", - kongFile: "testdata/sync/003-create-a-plugin/kong.yaml", + name: "creates a service", + kongFile: "testdata/sync/001-create-a-service/kong.yaml", expectedState: utils.KongRawState{ - Plugins: plugin_143_151, + Services: svc1_207, + }, + }, + { + name: "create services and routes", + kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + Routes: route1_20x, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", "==1.4.3") - teardown := setup(t) - defer teardown(t) + runWhen(t, "kong", ">2.6.9 <3.0.0") + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1171,8 +1370,8 @@ func Test_Sync_BasicAuth_Plugin_1_4_3(t *testing.T) { } // test scope: -// - 1.5.0.11+enterprise -func Test_Sync_BasicAuth_Plugin_Earlier_Than_1_5_1(t *testing.T) { +// - 3.x +func Test_Sync_ServicesRoutes_From_3x(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1180,24 +1379,139 @@ func Test_Sync_BasicAuth_Plugin_Earlier_Than_1_5_1(t *testing.T) { } tests := []struct { - name string - kongFile string - initialKongFile string - expectedState utils.KongRawState + name string + kongFile string + expectedState utils.KongRawState }{ { - name: "create a plugin", - kongFile: "testdata/sync/003-create-a-plugin/kong.yaml", + name: "creates a service", + kongFile: "testdata/sync/001-create-a-service/kong3x.yaml", expectedState: utils.KongRawState{ - Plugins: plugin, + Services: svc1_207, }, }, - } + { + name: "create services and routes", + kongFile: "testdata/sync/002-create-services-and-routes/kong3x.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + Routes: route1_20x, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhenKongOrKonnect(t, ">=3.0.0") + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} + +// test scope: +// - konnect +func Test_Sync_ServicesRoutes_Konnect(t *testing.T) { + // setup stage + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + tests := []struct { + name string + kongFile string + expectedState utils.KongRawState + }{ + { + name: "creates a service", + kongFile: "testdata/sync/001-create-a-service/kong3x.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + }, + }, + { + name: "create services and routes", + kongFile: "testdata/sync/002-create-services-and-routes/kong3x.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + Routes: route1_20x, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "konnect", "") + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} + +// test scope: +// - 1.4.3 +func Test_Sync_BasicAuth_Plugin_1_4_3(t *testing.T) { + // setup stage + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + tests := []struct { + name string + kongFile string + initialKongFile string + expectedState utils.KongRawState + }{ + { + name: "create a plugin", + kongFile: "testdata/sync/003-create-a-plugin/kong.yaml", + expectedState: utils.KongRawState{ + Plugins: plugin_143_151, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "kong", "==1.4.3") + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} + +// test scope: +// - 1.5.0.11+enterprise +func Test_Sync_BasicAuth_Plugin_Earlier_Than_1_5_1(t *testing.T) { + // setup stage + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + tests := []struct { + name string + kongFile string + initialKongFile string + expectedState utils.KongRawState + }{ + { + name: "create a plugin", + kongFile: "testdata/sync/003-create-a-plugin/kong.yaml", + expectedState: utils.KongRawState{ + Plugins: plugin, + }, + }, + } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "<1.5.1 !1.4.3") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1231,8 +1545,7 @@ func Test_Sync_BasicAuth_Plugin_1_5_1(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "==1.5.1") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1281,8 +1594,7 @@ func Test_Sync_BasicAuth_Plugin_From_2_0_5_Till_2_8_0(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.0.5 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1316,8 +1628,7 @@ func Test_Sync_BasicAuth_Plugin_From_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1351,8 +1662,7 @@ func Test_Sync_BasicAuth_Plugin_Konnect(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "konnect", "") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1394,8 +1704,7 @@ func Test_Sync_Upstream_Target_Till_1_5_2(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "<=1.5.2") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, ignoreFields) @@ -1445,8 +1754,7 @@ func Test_Sync_Upstream_Target_From_2x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.1.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1481,8 +1789,7 @@ func Test_Sync_Upstream_Target_From_30(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.0.0 <3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1517,8 +1824,7 @@ func Test_Sync_Upstream_Target_From_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1553,8 +1859,7 @@ func Test_Sync_Upstream_Target_Konnect(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "konnect", "") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1597,8 +1902,7 @@ func Test_Sync_Upstreams_Target_ZeroWeight_2x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.4.1 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1633,8 +1937,7 @@ func Test_Sync_Upstreams_Target_ZeroWeight_30(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.0.0 <3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1669,8 +1972,7 @@ func Test_Sync_Upstreams_Target_ZeroWeight_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1705,8 +2007,7 @@ func Test_Sync_Upstreams_Target_ZeroWeight_Konnect(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "konnect", "") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1745,8 +2046,7 @@ func Test_Sync_RateLimitingPlugin(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "==2.7.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1789,8 +2089,7 @@ func Test_Sync_FillDefaults_Earlier_Than_1_5_1(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "<1.5.1 !1.4.3") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, ignoreFields) @@ -1828,8 +2127,7 @@ func Test_Sync_FillDefaults_From_2_0_5_To_2_1_4(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.0.5 <=2.1.4") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1874,8 +2172,7 @@ func Test_Sync_FillDefaults_From_2_2_1_to_2_6_0(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">2.2.1 <=2.6.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1915,8 +2212,7 @@ func Test_Sync_FillDefaults_From_2_6_9(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">2.6.9 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1952,8 +2248,7 @@ func Test_Sync_SkipCACert_2x(t *testing.T) { // here because the schema changed and the entities aren't the same // across all versions, even though the skip functionality works the same. runWhen(t, "kong", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile, "--skip-ca-certificates") testKongState(t, client, false, tc.expectedState, nil) @@ -1989,8 +2284,7 @@ func Test_Sync_SkipCACert_3x(t *testing.T) { // here because the schema changed and the entities aren't the same // across all versions, even though the skip functionality works the same. runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile, "--skip-ca-certificates") testKongState(t, client, false, tc.expectedState, nil) @@ -2095,8 +2389,7 @@ func Test_Sync_RBAC_2x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "enterprise", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile, "--rbac-resources-only") testKongState(t, client, false, tc.expectedState, nil) @@ -2201,8 +2494,7 @@ func Test_Sync_RBAC_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "enterprise", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile, "--rbac-resources-only") testKongState(t, client, false, tc.expectedState, nil) @@ -2235,8 +2527,7 @@ func Test_Sync_Create_Route_With_Service_Name_Reference_2x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2269,8 +2560,7 @@ func Test_Sync_Create_Route_With_Service_Name_Reference_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2308,8 +2598,7 @@ func Test_Sync_PluginsOnEntitiesTill_3_0_0(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.8.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2346,8 +2635,7 @@ func Test_Sync_PluginsOnEntitiesFrom_3_0_0(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2405,8 +2693,7 @@ func Test_Sync_PluginOrdering(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "enterprise", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2433,8 +2720,7 @@ func Test_Sync_Unsupported_Formats(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) err := sync(tc.kongFile) assert.Equal(t, err, tc.expectedError) @@ -2583,8 +2869,7 @@ func Test_Sync_Vault(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "enterprise", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2672,8 +2957,7 @@ func Test_Sync_UpdateUsernameInConsumerWithCustomID(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.8.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -2717,8 +3001,7 @@ func Test_Sync_UpdateConsumerWithCustomID(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.8.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -2762,8 +3045,7 @@ func Test_Sync_UpdateUsernameInConsumerWithCustomID_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -2807,8 +3089,7 @@ func Test_Sync_UpdateConsumerWithCustomID_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -2851,8 +3132,7 @@ func Test_Sync_ConsumerGroupsTill30(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "enterprise", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2894,9 +3174,8 @@ func Test_Sync_ConsumerGroups_31(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "enterprise", ">=3.1.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "enterprise", "==3.1.0") + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -2983,9 +3262,8 @@ func Test_Sync_ConsumerGroupsRLAFrom31(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "enterprise", "==3.0.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "enterprise", ">=3.0.0 <3.1.0") + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -3069,21 +3347,11 @@ func Test_Sync_ConsumerGroupsKonnect(t *testing.T) { ConsumerGroups: consumerGroupsWithTags, }, }, - { - name: "creates consumer groups and plugin", - kongFile: "testdata/sync/016-consumer-groups-and-plugins/kong3x.yaml", - kongFileInitial: "testdata/sync/016-consumer-groups-and-plugins/kong3x-initial.yaml", - expectedState: utils.KongRawState{ - Consumers: consumerGroupsConsumers, - ConsumerGroups: consumerGroupsWithRLAKonnect, - }, - }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "konnect", "") - teardown := setup(t) - defer teardown(t) + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -3167,8 +3435,7 @@ func Test_Sync_PluginInstanceName(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.2.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -3177,7 +3444,8 @@ func Test_Sync_PluginInstanceName(t *testing.T) { } // test scope: -// - 3.2.0+ +// - 3.2.x +// - 3.3.x func Test_Sync_SkipConsumers(t *testing.T) { // setup stage client, err := getTestClient() @@ -3212,9 +3480,8 @@ func Test_Sync_SkipConsumers(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "enterprise", ">=3.2.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "enterprise", ">=3.2.0 <3.4.0") + setup(t) if tc.skipConsumers { sync(tc.kongFile, "--skip-consumers") @@ -3226,103 +3493,448 @@ func Test_Sync_SkipConsumers(t *testing.T) { } } -// In the tests we're concerned only with the IDs and names of the entities we'll ignore other fields when comparing states. -var ignoreFieldsIrrelevantForIDsTests = []cmp.Option{ - cmpopts.IgnoreFields( - kong.Plugin{}, - "Config", - "Protocols", - "Enabled", - ), - cmpopts.IgnoreFields( - kong.Service{}, - "ConnectTimeout", - "Enabled", - "Host", - "Port", - "Protocol", - "ReadTimeout", - "WriteTimeout", - "Retries", - ), - cmpopts.IgnoreFields( - kong.Route{}, - "Paths", - "PathHandling", - "PreserveHost", - "Protocols", - "RegexPriority", - "StripPath", - "HTTPSRedirectStatusCode", - "Sources", - "Destinations", - "RequestBuffering", - "ResponseBuffering", - ), -} - // test scope: -// - 3.0.0+ -// - konnect -func Test_Sync_ChangingIDsWhileKeepingNames(t *testing.T) { - runWhenKongOrKonnect(t, ">=3.0.0") - +// - 3.4.x +func Test_Sync_SkipConsumers_34x(t *testing.T) { + runWhen(t, "enterprise", ">=3.4.0 <3.5.0") + // setup stage client, err := getTestClient() if err != nil { t.Errorf(err.Error()) } - // These are the IDs that should be present in Kong after the second sync in all cases. - var ( - expectedServiceID = kong.String("98076db2-28b6-423b-ba39-a797193017f7") - expectedRouteID = kong.String("97b6a97e-f3f7-4c47-857a-7464cb9e202b") - expectedConsumerID = kong.String("9a1e49a8-2536-41fa-a4e9-605bf218a4fa") - ) - - // These are the entities that should be present in Kong after the second sync in all cases. - var ( - expectedService = &kong.Service{ - Name: kong.String("s1"), - ID: expectedServiceID, - } - - expectedRoute = &kong.Route{ - Name: kong.String("r1"), - ID: expectedRouteID, - Service: &kong.Service{ - ID: expectedServiceID, - }, - } - - expectedConsumer = &kong.Consumer{ - Username: kong.String("c1"), - ID: expectedConsumerID, - } - - expectedPlugins = []*kong.Plugin{ - { - Name: kong.String("rate-limiting"), - Route: &kong.Route{ - ID: expectedRouteID, - }, + tests := []struct { + name string + kongFile string + skipConsumers bool + expectedState utils.KongRawState + }{ + { + name: "skip-consumers successfully", + kongFile: "testdata/sync/019-skip-consumers/kong34.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, }, - { - Name: kong.String("rate-limiting"), - Service: &kong.Service{ - ID: expectedServiceID, + skipConsumers: true, + }, + { + name: "do not skip consumers successfully", + kongFile: "testdata/sync/019-skip-consumers/kong34.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + Consumers: consumerGroupsConsumers, + ConsumerGroups: []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + Tags: kong.StringSlice("tag1", "tag3"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + Tags: kong.StringSlice("tag1", "tag2"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, }, - }, - { - Name: kong.String("rate-limiting"), - Consumer: &kong.Consumer{ - ID: expectedConsumerID, + Plugins: []*kong.Plugin{ + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("77e6691d-67c0-446a-9401-27be2b141aae"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(10)}, + "namespace": string("gold"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("5bcbd3a7-030b-4310-bd1d-2721ff85d236"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(7)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, }, }, - } - ) + skipConsumers: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + setup(t) - testCases := []struct { - name string + if tc.skipConsumers { + sync(tc.kongFile, "--skip-consumers") + } else { + sync(tc.kongFile) + } + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} + +// test scope: +// - konnect +func Test_Sync_SkipConsumers_Konnect(t *testing.T) { + runWhenKonnect(t) + // setup stage + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + tests := []struct { + name string + kongFile string + skipConsumers bool + expectedState utils.KongRawState + }{ + { + name: "skip-consumers successfully", + kongFile: "testdata/sync/019-skip-consumers/kong34.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + }, + skipConsumers: true, + }, + { + name: "do not skip consumers successfully", + kongFile: "testdata/sync/019-skip-consumers/kong34.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + Consumers: consumerGroupsConsumers, + ConsumerGroups: []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + Tags: kong.StringSlice("tag1", "tag3"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + Tags: kong.StringSlice("tag1", "tag2"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, + }, + Plugins: []*kong.Plugin{ + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("77e6691d-67c0-446a-9401-27be2b141aae"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(10)}, + "namespace": string("gold"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": nil, + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("5bcbd3a7-030b-4310-bd1d-2721ff85d236"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(7)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": nil, + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + }, + }, + skipConsumers: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "enterprise", ">=3.2.0") + setup(t) + + if tc.skipConsumers { + sync(tc.kongFile, "--skip-consumers") + } else { + sync(tc.kongFile) + } + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} + +// In the tests we're concerned only with the IDs and names of the entities we'll ignore other fields when comparing states. +var ignoreFieldsIrrelevantForIDsTests = []cmp.Option{ + cmpopts.IgnoreFields( + kong.Plugin{}, + "Config", + "Protocols", + "Enabled", + ), + cmpopts.IgnoreFields( + kong.Service{}, + "ConnectTimeout", + "Enabled", + "Host", + "Port", + "Protocol", + "ReadTimeout", + "WriteTimeout", + "Retries", + ), + cmpopts.IgnoreFields( + kong.Route{}, + "Paths", + "PathHandling", + "PreserveHost", + "Protocols", + "RegexPriority", + "StripPath", + "HTTPSRedirectStatusCode", + "Sources", + "Destinations", + "RequestBuffering", + "ResponseBuffering", + ), +} + +// test scope: +// - 3.0.0+ +// - konnect +func Test_Sync_ChangingIDsWhileKeepingNames(t *testing.T) { + runWhenKongOrKonnect(t, ">=3.0.0") + + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + // These are the IDs that should be present in Kong after the second sync in all cases. + var ( + expectedServiceID = kong.String("98076db2-28b6-423b-ba39-a797193017f7") + expectedRouteID = kong.String("97b6a97e-f3f7-4c47-857a-7464cb9e202b") + expectedConsumerID = kong.String("9a1e49a8-2536-41fa-a4e9-605bf218a4fa") + ) + + // These are the entities that should be present in Kong after the second sync in all cases. + var ( + expectedService = &kong.Service{ + Name: kong.String("s1"), + ID: expectedServiceID, + } + + expectedRoute = &kong.Route{ + Name: kong.String("r1"), + ID: expectedRouteID, + Service: &kong.Service{ + ID: expectedServiceID, + }, + } + + expectedConsumer = &kong.Consumer{ + Username: kong.String("c1"), + ID: expectedConsumerID, + } + + expectedPlugins = []*kong.Plugin{ + { + Name: kong.String("rate-limiting"), + Route: &kong.Route{ + ID: expectedRouteID, + }, + }, + { + Name: kong.String("rate-limiting"), + Service: &kong.Service{ + ID: expectedServiceID, + }, + }, + { + Name: kong.String("rate-limiting"), + Consumer: &kong.Consumer{ + ID: expectedConsumerID, + }, + }, + } + ) + + testCases := []struct { + name string beforeConfig string }{ { @@ -3341,8 +3953,7 @@ func Test_Sync_ChangingIDsWhileKeepingNames(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - teardown := setup(t) - defer teardown(t) + setup(t) // First, create the entities with the original IDs. err = sync(tc.beforeConfig) @@ -3368,6 +3979,7 @@ func Test_Sync_ChangingIDsWhileKeepingNames(t *testing.T) { // - konnect func Test_Sync_UpdateWithExplicitIDs(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") + setup(t) client, err := getTestClient() if err != nil { @@ -3420,6 +4032,7 @@ func Test_Sync_UpdateWithExplicitIDs(t *testing.T) { // - konnect func Test_Sync_UpdateWithExplicitIDsWithNoNames(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") + setup(t) client, err := getTestClient() if err != nil { @@ -3458,3 +4071,502 @@ func Test_Sync_UpdateWithExplicitIDsWithNoNames(t *testing.T) { }, }, ignoreFieldsIrrelevantForIDsTests) } + +// test scope: +// - 3.0.0+ +// - konnect +func Test_Sync_CreateCertificateWithSNIs(t *testing.T) { + runWhenKongOrKonnect(t, ">=3.0.0") + setup(t) + + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + err = sync("testdata/sync/023-create-and-update-certificate-with-snis/initial.yaml") + require.NoError(t, err) + + // To ignore noise, we ignore the Key and Cert fields because they are not relevant for this test. + ignoredFields := []cmp.Option{ + cmpopts.IgnoreFields( + kong.Certificate{}, + "Key", + "Cert", + ), + } + + testKongState(t, client, false, utils.KongRawState{ + Certificates: []*kong.Certificate{ + { + ID: kong.String("c75a775b-3a32-4b73-8e05-f68169c23941"), + Tags: kong.StringSlice("before"), + }, + }, + SNIs: []*kong.SNI{ + { + Name: kong.String("example.com"), + Certificate: &kong.Certificate{ + ID: kong.String("c75a775b-3a32-4b73-8e05-f68169c23941"), + }, + }, + }, + }, ignoredFields) + + err = sync("testdata/sync/023-create-and-update-certificate-with-snis/update.yaml") + require.NoError(t, err) + + testKongState(t, client, false, utils.KongRawState{ + Certificates: []*kong.Certificate{ + { + ID: kong.String("c75a775b-3a32-4b73-8e05-f68169c23941"), + Tags: kong.StringSlice("after"), // Tag should be updated. + }, + }, + SNIs: []*kong.SNI{ + { + Name: kong.String("example.com"), + Certificate: &kong.Certificate{ + ID: kong.String("c75a775b-3a32-4b73-8e05-f68169c23941"), + }, + }, + }, + }, ignoredFields) +} + +// test scope: +// - 3.0.0+ +// - konnect +func Test_Sync_ConsumersWithCustomIDAndOrUsername(t *testing.T) { + runWhenKongOrKonnect(t, ">=3.0.0") + setup(t) + + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + err = sync("testdata/sync/024-consumers-with-custom_id-and-username/kong3x.yaml") + require.NoError(t, err) + + testKongState(t, client, false, utils.KongRawState{ + Consumers: []*kong.Consumer{ + { + ID: kong.String("ce49186d-7670-445d-a218-897631b29ada"), + Username: kong.String("Foo"), + CustomID: kong.String("foo"), + }, + { + ID: kong.String("7820f383-7b77-4fcc-af7f-14ff3e256693"), + Username: kong.String("foo"), + CustomID: kong.String("bar"), + }, + { + ID: kong.String("18c62c3c-12cc-429a-8e5a-57f2c3691a6b"), + CustomID: kong.String("custom_id_only"), + }, + { + ID: kong.String("8ef278c9-48c1-43e1-b665-e9bc18fab4c8"), + Username: kong.String("username_only"), + }, + }, + }, nil) +} + +// This test has 2 goals: +// - make sure consumer groups scoped plugins can be configured correctly in Kong +// - the actual consumer groups functionality works once set +// +// This is achieved via configuring: +// - 3 consumers: +// - 1 belonging to Gold Consumer Group +// - 1 belonging to Silver Consumer Group +// - 1 not belonging to any Consumer Group +// +// - 3 key-auths, one for each consumer +// - 1 global key-auth plugin +// - 2 consumer group +// - 1 global RLA plugin +// - 2 RLA plugins, scoped to the related consumer groups +// - 1 service pointing to mockbin.org +// - 1 route proxying the above service +// +// Once the configuration is verified to be matching in Kong, +// we then check whether the specific RLA configuration is correctly applied: consumers +// not belonging to the consumer group should be limited to 5 requests +// every 30s, while consumers belonging to the 'gold' and 'silver' consumer groups +// should be allowed to run respectively 10 and 7 requests in the same timeframe. +// In order to make sure this is the case, we run requests in a loop +// for all consumers and then check at what point they start to receive 429. +func Test_Sync_ConsumerGroupsScopedPlugins(t *testing.T) { + const ( + maxGoldRequestsNumber = 10 + maxSilverRequestsNumber = 7 + maxRegularRequestsNumber = 5 + ) + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + tests := []struct { + name string + kongFile string + expectedState utils.KongRawState + }{ + { + name: "creates consumer groups scoped plugins", + kongFile: "testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml", + expectedState: utils.KongRawState{ + Consumers: consumerGroupsConsumers, + ConsumerGroups: []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, + }, + Plugins: consumerGroupScopedPlugins, + Services: svc1_207, + Routes: route1_20x, + KeyAuths: []*kong.KeyAuth{ + { + Consumer: &kong.Consumer{ + ID: kong.String("87095815-5395-454e-8c18-a11c9bc0ef04"), + }, + Key: kong.String("i-am-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("5a5b9369-baeb-4faa-a902-c40ccdc2928e"), + }, + Key: kong.String("i-am-not-so-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("e894ea9e-ad08-4acf-a960-5a23aa7701c7"), + }, + Key: kong.String("i-am-just-average"), + }, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "enterprise", ">=3.4.0 <3.5.0") + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + + // Kong proxy may need a bit to be ready. + time.Sleep(time.Second * 10) + + // build simple http client + client := &http.Client{} + + // test 'foo' consumer (part of 'gold' group) + req, err := http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-special") + n := 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxGoldRequestsNumber, n) + + // test 'bar' consumer (part of 'silver' group) + req, err = http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-not-so-special") + n = 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxSilverRequestsNumber, n) + + // test 'baz' consumer (not part of any group) + req, err = http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-just-average") + n = 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxRegularRequestsNumber, n) + }) + } +} + +func Test_Sync_ConsumerGroupsScopedPlugins_After350(t *testing.T) { + const ( + maxGoldRequestsNumber = 10 + maxSilverRequestsNumber = 7 + maxRegularRequestsNumber = 5 + ) + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + tests := []struct { + name string + kongFile string + expectedState utils.KongRawState + }{ + { + name: "creates consumer groups scoped plugins", + kongFile: "testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml", + expectedState: utils.KongRawState{ + Consumers: consumerGroupsConsumers, + ConsumerGroups: []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, + }, + Plugins: consumerGroupScopedPlugins35x, + Services: svc1_207, + Routes: route1_20x, + KeyAuths: []*kong.KeyAuth{ + { + Consumer: &kong.Consumer{ + ID: kong.String("87095815-5395-454e-8c18-a11c9bc0ef04"), + }, + Key: kong.String("i-am-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("5a5b9369-baeb-4faa-a902-c40ccdc2928e"), + }, + Key: kong.String("i-am-not-so-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("e894ea9e-ad08-4acf-a960-5a23aa7701c7"), + }, + Key: kong.String("i-am-just-average"), + }, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "enterprise", ">=3.5.0") + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + + // Kong proxy may need a bit to be ready. + time.Sleep(time.Second * 10) + + // build simple http client + client := &http.Client{} + + // test 'foo' consumer (part of 'gold' group) + req, err := http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-special") + n := 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxGoldRequestsNumber, n) + + // test 'bar' consumer (part of 'silver' group) + req, err = http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-not-so-special") + n = 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxSilverRequestsNumber, n) + + // test 'baz' consumer (not part of any group) + req, err = http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-just-average") + n = 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxRegularRequestsNumber, n) + }) + } +} + +// test scope: +// - > 3.4.0 +func Test_Sync_ConsumerGroupsScopedPlugins_Post340(t *testing.T) { + tests := []struct { + name string + kongFile string + expectedError error + }{ + { + name: "attempt to create deprecated consumer groups configuration with Kong version >= 3.4.0 fails", + kongFile: "testdata/sync/017-consumer-groups-rla-application/kong3x.yaml", + expectedError: fmt.Errorf("building state: %v", utils.ErrorConsumerGroupUpgrade), + }, + { + name: "empty deprecated consumer groups configuration fields do not fail with Kong version >= 3.4.0", + kongFile: "testdata/sync/017-consumer-groups-rla-application/kong3x-empty-application.yaml", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "enterprise", ">=3.4.0") + setup(t) + + err := sync(tc.kongFile) + if tc.expectedError == nil { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedError.Error()) + } + }) + } +} + +func Test_Sync_ConsumerGroupsScopedPluginsKonnect(t *testing.T) { + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + tests := []struct { + name string + kongFile string + expectedState utils.KongRawState + }{ + { + name: "creates consumer groups scoped plugins", + kongFile: "testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml", + expectedState: utils.KongRawState{ + Consumers: consumerGroupsConsumers, + ConsumerGroups: []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, + }, + Plugins: consumerGroupScopedPlugins, + Services: svc1_207, + Routes: route1_20x, + KeyAuths: []*kong.KeyAuth{ + { + Consumer: &kong.Consumer{ + ID: kong.String("87095815-5395-454e-8c18-a11c9bc0ef04"), + }, + Key: kong.String("i-am-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("5a5b9369-baeb-4faa-a902-c40ccdc2928e"), + }, + Key: kong.String("i-am-not-so-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("e894ea9e-ad08-4acf-a960-5a23aa7701c7"), + }, + Key: kong.String("i-am-just-average"), + }, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhenKonnect(t) + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} diff --git a/tests/integration/test_utils.go b/tests/integration/test_utils.go index 228db1e28..28d7c16c8 100644 --- a/tests/integration/test_utils.go +++ b/tests/integration/test_utils.go @@ -95,8 +95,16 @@ func sortSlices(x, y interface{}) bool { yName = *yEntity.Prefix case *kong.Consumer: yEntity := y.(*kong.Consumer) - xName = *xEntity.Username - yName = *yEntity.Username + if xEntity.Username != nil { + xName = *xEntity.Username + } else { + xName = *xEntity.ID + } + if yEntity.Username != nil { + yName = *yEntity.Username + } else { + yName = *yEntity.ID + } case *kong.ConsumerGroup: yEntity := y.(*kong.ConsumerGroup) xName = *xEntity.Name @@ -126,8 +134,8 @@ func sortSlices(x, y interface{}) bool { if xEntity.Consumer != nil { xName += *xEntity.Consumer.ID } - if xEntity.Consumer != nil { - xName += *xEntity.Consumer.ID + if xEntity.ConsumerGroup != nil { + xName += *xEntity.ConsumerGroup.ID } if yEntity.Route != nil { yName += *yEntity.Route.ID @@ -138,6 +146,9 @@ func sortSlices(x, y interface{}) bool { if yEntity.Consumer != nil { yName += *yEntity.Consumer.ID } + if yEntity.ConsumerGroup != nil { + yName += *yEntity.ConsumerGroup.ID + } } return xName < yName } @@ -207,12 +218,17 @@ func readFile(filepath string) (string, error) { return string(content), nil } -func setup(_ *testing.T) func(t *testing.T) { +// setup sets deck env variable to prevent analytics in tests and registers reset +// command with t.Cleanup(). +// +// NOTE: Can't be called with tests running t.Parallel() because of the usage +// of t.Setenv(). +func setup(t *testing.T) { // disable analytics for integration tests - os.Setenv("DECK_ANALYTICS", "off") - return func(t *testing.T) { + t.Setenv("DECK_ANALYTICS", "off") + t.Cleanup(func() { reset(t) - } + }) } func sync(kongFile string, opts ...string) error { diff --git a/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-34.yaml b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-34.yaml new file mode 100644 index 000000000..ee591dea4 --- /dev/null +++ b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-34.yaml @@ -0,0 +1,60 @@ +_format_version: "3.0" +consumer_groups: +- name: basic + plugins: + - config: + consumer_groups: null + dictionary_name: kong_rate_limiting_counters + disable_penalty: false + enforce_consumer_groups: false + error_code: 429 + error_message: API rate limit exceeded + header_name: null + hide_client_headers: false + identifier: consumer + limit: + - 30000 + namespace: basic + path: null + redis: + cluster_addresses: null + connect_timeout: null + database: 0 + host: null + keepalive_backlog: null + keepalive_pool_size: 30 + password: null + port: null + read_timeout: null + send_timeout: null + sentinel_addresses: null + sentinel_master: null + sentinel_password: null + sentinel_role: null + sentinel_username: null + server_name: null + ssl: false + ssl_verify: false + timeout: 2000 + username: null + retry_after_jitter_max: 0 + strategy: local + sync_rate: -1 + window_size: + - 2628000 + window_type: sliding + name: rate-limiting-advanced +consumers: +- groups: + - name: basic + username: foo +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 diff --git a/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-35.yaml b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-35.yaml new file mode 100644 index 000000000..86cd73d93 --- /dev/null +++ b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-35.yaml @@ -0,0 +1,60 @@ +_format_version: "3.0" +consumer_groups: +- name: basic + plugins: + - config: + consumer_groups: null + dictionary_name: kong_rate_limiting_counters + disable_penalty: false + enforce_consumer_groups: false + error_code: 429 + error_message: API rate limit exceeded + header_name: null + hide_client_headers: false + identifier: consumer + limit: + - 30000 + namespace: basic + path: null + redis: + cluster_addresses: null + connect_timeout: null + database: 0 + host: null + keepalive_backlog: null + keepalive_pool_size: 256 + password: null + port: null + read_timeout: null + send_timeout: null + sentinel_addresses: null + sentinel_master: null + sentinel_password: null + sentinel_role: null + sentinel_username: null + server_name: null + ssl: false + ssl_verify: false + timeout: 2000 + username: null + retry_after_jitter_max: 0 + strategy: local + sync_rate: -1 + window_size: + - 2628000 + window_type: sliding + name: rate-limiting-advanced +consumers: +- groups: + - name: basic + username: foo +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 diff --git a/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip_konnect.yaml b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip_konnect.yaml new file mode 100644 index 000000000..b055aaeb1 --- /dev/null +++ b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip_konnect.yaml @@ -0,0 +1,62 @@ +_format_version: "3.0" +_konnect: + runtime_group_name: default +consumer_groups: +- name: basic + plugins: + - config: + consumer_groups: null + dictionary_name: kong_rate_limiting_counters + disable_penalty: false + enforce_consumer_groups: false + error_code: 429 + error_message: API rate limit exceeded + header_name: null + hide_client_headers: false + identifier: consumer + limit: + - 30000 + namespace: basic + path: null + redis: + cluster_addresses: null + connect_timeout: null + database: 0 + host: null + keepalive_backlog: null + keepalive_pool_size: 30 + password: null + port: null + read_timeout: null + send_timeout: null + sentinel_addresses: null + sentinel_master: null + sentinel_password: null + sentinel_role: null + sentinel_username: null + server_name: null + ssl: false + ssl_verify: false + timeout: 2000 + username: null + retry_after_jitter_max: 0 + strategy: local + sync_rate: null + window_size: + - 2628000 + window_type: sliding + name: rate-limiting-advanced +consumers: +- groups: + - name: basic + username: foo +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 diff --git a/tests/integration/testdata/dump/002-skip-consumers/expected_konnect.yaml b/tests/integration/testdata/dump/002-skip-consumers/expected_konnect.yaml new file mode 100644 index 000000000..d27edcddd --- /dev/null +++ b/tests/integration/testdata/dump/002-skip-consumers/expected_konnect.yaml @@ -0,0 +1,13 @@ +_format_version: "3.0" +_konnect: + runtime_group_name: default +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 diff --git a/tests/integration/testdata/dump/002-skip-consumers/kong34.yaml b/tests/integration/testdata/dump/002-skip-consumers/kong34.yaml new file mode 100644 index 000000000..5a04f7573 --- /dev/null +++ b/tests/integration/testdata/dump/002-skip-consumers/kong34.yaml @@ -0,0 +1,19 @@ +_format_version: "3.0" +consumer_groups: +- name: basic + plugins: + - config: + limit: + - 30000 + window_size: + - 2628000 + window_type: sliding + namespace: basic + name: rate-limiting-advanced +consumers: + - username: foo + groups: + - name: basic +services: +- name: svc1 + host: mockbin.org \ No newline at end of file diff --git a/tests/integration/testdata/sync/017-consumer-groups-rla-application/kong3x-empty-application.yaml b/tests/integration/testdata/sync/017-consumer-groups-rla-application/kong3x-empty-application.yaml new file mode 100644 index 000000000..cd5cde8a5 --- /dev/null +++ b/tests/integration/testdata/sync/017-consumer-groups-rla-application/kong3x-empty-application.yaml @@ -0,0 +1,48 @@ +_format_version: "3.0" +plugins: +- config: + consumer_groups: null + dictionary_name: kong_rate_limiting_counters + enforce_consumer_groups: false + header_name: null + hide_client_headers: false + identifier: consumer + limit: + - 5 + namespace: dNRC6xKsRL8Koc1uVYA4Nki6DLW7XIdx + path: null + redis: + cluster_addresses: null + connect_timeout: null + database: 0 + host: null + keepalive_backlog: null + keepalive_pool_size: 30 + password: null + port: null + read_timeout: null + send_timeout: null + sentinel_addresses: null + sentinel_master: null + sentinel_password: null + sentinel_role: null + sentinel_username: null + server_name: null + ssl: false + ssl_verify: false + timeout: 2000 + username: null + retry_after_jitter_max: 0 + strategy: local + sync_rate: -1 + window_size: + - 60 + window_type: sliding + enabled: true + name: rate-limiting-advanced + protocols: + - grpc + - grpcs + - http + - https + diff --git a/tests/integration/testdata/sync/019-skip-consumers/kong34.yaml b/tests/integration/testdata/sync/019-skip-consumers/kong34.yaml new file mode 100644 index 000000000..433ef4290 --- /dev/null +++ b/tests/integration/testdata/sync/019-skip-consumers/kong34.yaml @@ -0,0 +1,49 @@ +_format_version: "3.0" +consumer_groups: +- id: 77e6691d-67c0-446a-9401-27be2b141aae + name: gold + tags: + - tag1 + - tag2 + plugins: + - name: rate-limiting-advanced + config: + namespace: gold + limit: + - 10 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding +- id: 5bcbd3a7-030b-4310-bd1d-2721ff85d236 + name: silver + tags: + - tag1 + - tag3 + plugins: + - name: rate-limiting-advanced + config: + namespace: silver + limit: + - 7 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding +consumers: +- groups: + - name: silver + username: bar +- username: baz +- groups: + - name: gold + username: foo +services: +- connect_timeout: 60000 + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 diff --git a/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/initial.yaml b/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/initial.yaml new file mode 100644 index 000000000..65f2781fd --- /dev/null +++ b/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/initial.yaml @@ -0,0 +1,55 @@ +_format_version: "3.0" +certificates: + - id: 13c562a1-191c-4464-9b18-e5222b46035b + cert: | + -----BEGIN CERTIFICATE----- + MIIC1jCCAb4CCQCt23nwvxSCvjANBgkqhkiG9w0BAQsFADAtMRYwFAYDVQQDDA0q + LmV4YW1wbGUuY29tMRMwEQYDVQQKDAprb25naHEub3JnMB4XDTE4MTIzMTIwMTkw + MVoXDTE5MTIzMTIwMTkwMVowLTEWMBQGA1UEAwwNKi5leGFtcGxlLmNvbTETMBEG + A1UECgwKa29uZ2hxLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB + AKj/2r1AXo9x+2Csrd0SHbpnzuW+xYqgsd+YA9ZrZNV7SZGSbaZymsRMz8wg5OIU + iUik2GM1749/lYvojLFStBPy9UY/gd++5f3wLp4xHiI+IU2XQ97otXKGfyh36RmN + dKDqPLN8BG3R346s/y1GOulFvLthYmZVYF9ufHiqimfEDSbTt79P5C3X0Rw/afK1 + GjHEJPCB/XkZ6lkcEyL6LqZI5oBigDqa9hI/nWLxEzfm8pgosiS38p9TAijlOkpm + tX2p2b1pktlNIy3rxsqj6IynN9Wc7FpV1N4HoPKV7vQQ08hjwW6WfanVthaaJosj + Vr2TBCJ1ltAmsb+5B2VPYVkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAnByTyQfV + 3LkwuoWS57CWcqbNw/cHnv/ChzmIv+6mIXvDBSvCgrPZIWCpaCfYRG6R51E44fr/ + 8V1AKT0Zt15DjrXEEcIGQgsIDO91/wlL091fTAUzSbL0yt7HTlm8sX6xndPNAZrq + cfcIPVMxknfqPy2VqS4IrNC03pHkDKtokphBjVUlkiWsdcq+fHYbS2xL2d1Da/uN + hX/iwgo+v5gOF5xtaXx7D7L3Cf+MHb/MOXWPfYXNiTpSBVX8/Kx5RP+QLI16nWvw + lrijTlXZFR8NIZBrCo/QZ2cNbUAbN3R0n+/kMFubxBL8WEm6Qhi9jBjbJeDMspd8 + C+/TZJQMpx5vyA== + -----END CERTIFICATE----- + key: | + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCo/9q9QF6Pcftg + rK3dEh26Z87lvsWKoLHfmAPWa2TVe0mRkm2mcprETM/MIOTiFIlIpNhjNe+Pf5WL + 6IyxUrQT8vVGP4HfvuX98C6eMR4iPiFNl0Pe6LVyhn8od+kZjXSg6jyzfARt0d+O + rP8tRjrpRby7YWJmVWBfbnx4qopnxA0m07e/T+Qt19EcP2nytRoxxCTwgf15GepZ + HBMi+i6mSOaAYoA6mvYSP51i8RM35vKYKLIkt/KfUwIo5TpKZrV9qdm9aZLZTSMt + 68bKo+iMpzfVnOxaVdTeB6Dyle70ENPIY8Fuln2p1bYWmiaLI1a9kwQidZbQJrG/ + uQdlT2FZAgMBAAECggEAVnyRcda2Tcy0K7ZTR9aUlie370VhDN/OB7JhDGNreAEf + FjuMl+kAoUL5+OpAmB6QXzfVcXhRv+s4GiCJl9nORINK2Id5rIqiYwF+qgBS/o0z + N+UYm8QVz6Va/9fV1/jXXd5h8Cygi58jPH32HTJaxbSlsHNXCy3YIx6E3q/QIueR + 6ZdSXPqMEqxEU19M9jW8UeiRFrpmcyYxVpfxYIY/+O9lYjSpaeLs7hZeCP9PqWXA + Sxz2CnHZ8BcsDxAyuoHoVw+kjMpUMvA3sD4lwkV8BAYzfLmQf6PR83SFNsrE8XYu + /8WnQuCuytcl8Zg55R6tGCvf6Wyyf+MDRPwv/43QMQKBgQDbqK9Dq54k+EHgSNnP + K6AhNjFd6aqcNC1kom/sSlWBnuA/BEqJMECr8S2dYvzONUPPfX5NNUjB4Vw3Qw7a + pUgKuCQoVpzpZs5m1bk78itWDtA84LjkXfdejnUXVw/aVxLCM5QV9aEkm/dEWWMI + P1WTYVoWoZCLlEE08q0AvZQcdQKBgQDE9ZCmc6ncmhnQftuRj5PnXG2a79MLCT61 + sCEBDVvkcUJVqbzwGRLwRkdIzLgvmiuP+SukHgyfr8/RXG99xEW/q7NDrtEcqfXP + 19QXwOIp5NwDnOXyAlXiyZ50fCE2tSo2wP485+NIhmKj5Zt6y/DL6Qbc5k73XmK4 + KX5Ej15k1QKBgQCc6KeiIFLMt+Ze78tfORue/dZP7p3oDUGr1Hk9AnCIMlSfz1Hr + I+Per17VQaOzLcttyYhSYNDDZld4RlezCkQnHBkAE7bs53pjbSJv1vLr+5L3GdQZ + laIiEoNEE/YIExEcVrne4eKlgyAj2/JpLszThcRTzD+z5UibKQs6LzJBDQKBgDVa + dAGzCUt57w48nwvyQdWFgydaWef+bB9Zg8c+MCtUxuxfm4/Kqwetcff1hNtYPv60 + N68weKj1Pi1vhcAi3+YJA/mMrJbAL5dK1uhMVreUiEjuQpfpLAzQIv1Y9sJUFwhY + BUbIZhgqVyQguZptDmCeUj6aoL9/sOxESTEXSTG1AoGBAMQ5iJZMsdLCERv0+6Y1 + F/t/YSW8cugB3vdV9jHZuosoprz48p92pYP8OdQc70H5hZt53hoYNgYFSd+MU6H1 + hJCaXTsiP4IUmBjiwzSp3o1ctP8lWvnyJpAadYdDhaDaAAoaMjCo9cm5OMwc8t8x + hwAPXV2cgWH8fPcT9NLAcwWk + -----END PRIVATE KEY----- + tags: + - before + snis: + - name: example.com diff --git a/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/update.yaml b/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/update.yaml new file mode 100644 index 000000000..7256a7d1b --- /dev/null +++ b/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/update.yaml @@ -0,0 +1,55 @@ +_format_version: "3.0" +certificates: + - id: 13c562a1-191c-4464-9b18-e5222b46035b + cert: | + -----BEGIN CERTIFICATE----- + MIIC1jCCAb4CCQCt23nwvxSCvjANBgkqhkiG9w0BAQsFADAtMRYwFAYDVQQDDA0q + LmV4YW1wbGUuY29tMRMwEQYDVQQKDAprb25naHEub3JnMB4XDTE4MTIzMTIwMTkw + MVoXDTE5MTIzMTIwMTkwMVowLTEWMBQGA1UEAwwNKi5leGFtcGxlLmNvbTETMBEG + A1UECgwKa29uZ2hxLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB + AKj/2r1AXo9x+2Csrd0SHbpnzuW+xYqgsd+YA9ZrZNV7SZGSbaZymsRMz8wg5OIU + iUik2GM1749/lYvojLFStBPy9UY/gd++5f3wLp4xHiI+IU2XQ97otXKGfyh36RmN + dKDqPLN8BG3R346s/y1GOulFvLthYmZVYF9ufHiqimfEDSbTt79P5C3X0Rw/afK1 + GjHEJPCB/XkZ6lkcEyL6LqZI5oBigDqa9hI/nWLxEzfm8pgosiS38p9TAijlOkpm + tX2p2b1pktlNIy3rxsqj6IynN9Wc7FpV1N4HoPKV7vQQ08hjwW6WfanVthaaJosj + Vr2TBCJ1ltAmsb+5B2VPYVkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAnByTyQfV + 3LkwuoWS57CWcqbNw/cHnv/ChzmIv+6mIXvDBSvCgrPZIWCpaCfYRG6R51E44fr/ + 8V1AKT0Zt15DjrXEEcIGQgsIDO91/wlL091fTAUzSbL0yt7HTlm8sX6xndPNAZrq + cfcIPVMxknfqPy2VqS4IrNC03pHkDKtokphBjVUlkiWsdcq+fHYbS2xL2d1Da/uN + hX/iwgo+v5gOF5xtaXx7D7L3Cf+MHb/MOXWPfYXNiTpSBVX8/Kx5RP+QLI16nWvw + lrijTlXZFR8NIZBrCo/QZ2cNbUAbN3R0n+/kMFubxBL8WEm6Qhi9jBjbJeDMspd8 + C+/TZJQMpx5vyA== + -----END CERTIFICATE----- + key: | + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCo/9q9QF6Pcftg + rK3dEh26Z87lvsWKoLHfmAPWa2TVe0mRkm2mcprETM/MIOTiFIlIpNhjNe+Pf5WL + 6IyxUrQT8vVGP4HfvuX98C6eMR4iPiFNl0Pe6LVyhn8od+kZjXSg6jyzfARt0d+O + rP8tRjrpRby7YWJmVWBfbnx4qopnxA0m07e/T+Qt19EcP2nytRoxxCTwgf15GepZ + HBMi+i6mSOaAYoA6mvYSP51i8RM35vKYKLIkt/KfUwIo5TpKZrV9qdm9aZLZTSMt + 68bKo+iMpzfVnOxaVdTeB6Dyle70ENPIY8Fuln2p1bYWmiaLI1a9kwQidZbQJrG/ + uQdlT2FZAgMBAAECggEAVnyRcda2Tcy0K7ZTR9aUlie370VhDN/OB7JhDGNreAEf + FjuMl+kAoUL5+OpAmB6QXzfVcXhRv+s4GiCJl9nORINK2Id5rIqiYwF+qgBS/o0z + N+UYm8QVz6Va/9fV1/jXXd5h8Cygi58jPH32HTJaxbSlsHNXCy3YIx6E3q/QIueR + 6ZdSXPqMEqxEU19M9jW8UeiRFrpmcyYxVpfxYIY/+O9lYjSpaeLs7hZeCP9PqWXA + Sxz2CnHZ8BcsDxAyuoHoVw+kjMpUMvA3sD4lwkV8BAYzfLmQf6PR83SFNsrE8XYu + /8WnQuCuytcl8Zg55R6tGCvf6Wyyf+MDRPwv/43QMQKBgQDbqK9Dq54k+EHgSNnP + K6AhNjFd6aqcNC1kom/sSlWBnuA/BEqJMECr8S2dYvzONUPPfX5NNUjB4Vw3Qw7a + pUgKuCQoVpzpZs5m1bk78itWDtA84LjkXfdejnUXVw/aVxLCM5QV9aEkm/dEWWMI + P1WTYVoWoZCLlEE08q0AvZQcdQKBgQDE9ZCmc6ncmhnQftuRj5PnXG2a79MLCT61 + sCEBDVvkcUJVqbzwGRLwRkdIzLgvmiuP+SukHgyfr8/RXG99xEW/q7NDrtEcqfXP + 19QXwOIp5NwDnOXyAlXiyZ50fCE2tSo2wP485+NIhmKj5Zt6y/DL6Qbc5k73XmK4 + KX5Ej15k1QKBgQCc6KeiIFLMt+Ze78tfORue/dZP7p3oDUGr1Hk9AnCIMlSfz1Hr + I+Per17VQaOzLcttyYhSYNDDZld4RlezCkQnHBkAE7bs53pjbSJv1vLr+5L3GdQZ + laIiEoNEE/YIExEcVrne4eKlgyAj2/JpLszThcRTzD+z5UibKQs6LzJBDQKBgDVa + dAGzCUt57w48nwvyQdWFgydaWef+bB9Zg8c+MCtUxuxfm4/Kqwetcff1hNtYPv60 + N68weKj1Pi1vhcAi3+YJA/mMrJbAL5dK1uhMVreUiEjuQpfpLAzQIv1Y9sJUFwhY + BUbIZhgqVyQguZptDmCeUj6aoL9/sOxESTEXSTG1AoGBAMQ5iJZMsdLCERv0+6Y1 + F/t/YSW8cugB3vdV9jHZuosoprz48p92pYP8OdQc70H5hZt53hoYNgYFSd+MU6H1 + hJCaXTsiP4IUmBjiwzSp3o1ctP8lWvnyJpAadYdDhaDaAAoaMjCo9cm5OMwc8t8x + hwAPXV2cgWH8fPcT9NLAcwWk + -----END PRIVATE KEY----- + tags: + - after # Only this changes between initial and updated config. + snis: + - name: example.com diff --git a/tests/integration/testdata/sync/024-consumers-with-custom_id-and-username/kong3x.yaml b/tests/integration/testdata/sync/024-consumers-with-custom_id-and-username/kong3x.yaml new file mode 100644 index 000000000..c858f03d1 --- /dev/null +++ b/tests/integration/testdata/sync/024-consumers-with-custom_id-and-username/kong3x.yaml @@ -0,0 +1,12 @@ +_format_version: "3.0" +consumers: +- custom_id: foo + id: ce49186d-7670-445d-a218-897631b29ada + username: Foo +- custom_id: bar + id: 7820f383-7b77-4fcc-af7f-14ff3e256693 + username: foo +- custom_id: custom_id_only + id: 18c62c3c-12cc-429a-8e5a-57f2c3691a6b +- id: 8ef278c9-48c1-43e1-b665-e9bc18fab4c8 + username: username_only \ No newline at end of file diff --git a/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml b/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml new file mode 100644 index 000000000..ca22940db --- /dev/null +++ b/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml @@ -0,0 +1,79 @@ +_format_version: "3.0" +services: +- connect_timeout: 60000 + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + routes: + - name: r1 + id: 87b6a97e-f3f7-4c47-857a-7464cb9e202b + https_redirect_status_code: 301 + paths: + - /r1 + +consumer_groups: +- id: 5bcbd3a7-030b-4310-bd1d-2721ff85d236 + name: silver + consumers: + - username: bar + - username: baz + plugins: + - name: rate-limiting-advanced + config: + namespace: silver + limit: + - 7 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 +- id: 77e6691d-67c0-446a-9401-27be2b141aae + name: gold + consumers: + - username: foo + plugins: + - name: rate-limiting-advanced + config: + namespace: gold + limit: + - 10 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 +consumers: +- username: foo + keyauth_credentials: + - key: i-am-special + groups: + - name: gold +- username: bar + keyauth_credentials: + - key: i-am-not-so-special + groups: + - name: silver +- username: baz + keyauth_credentials: + - key: i-am-just-average +plugins: +- name: key-auth + enabled: true + protocols: + - http + - https +- name: rate-limiting-advanced + config: + namespace: silver + limit: + - 5 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 diff --git a/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/konnect.yaml b/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/konnect.yaml new file mode 100644 index 000000000..ca22940db --- /dev/null +++ b/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/konnect.yaml @@ -0,0 +1,79 @@ +_format_version: "3.0" +services: +- connect_timeout: 60000 + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + routes: + - name: r1 + id: 87b6a97e-f3f7-4c47-857a-7464cb9e202b + https_redirect_status_code: 301 + paths: + - /r1 + +consumer_groups: +- id: 5bcbd3a7-030b-4310-bd1d-2721ff85d236 + name: silver + consumers: + - username: bar + - username: baz + plugins: + - name: rate-limiting-advanced + config: + namespace: silver + limit: + - 7 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 +- id: 77e6691d-67c0-446a-9401-27be2b141aae + name: gold + consumers: + - username: foo + plugins: + - name: rate-limiting-advanced + config: + namespace: gold + limit: + - 10 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 +consumers: +- username: foo + keyauth_credentials: + - key: i-am-special + groups: + - name: gold +- username: bar + keyauth_credentials: + - key: i-am-not-so-special + groups: + - name: silver +- username: baz + keyauth_credentials: + - key: i-am-just-average +plugins: +- name: key-auth + enabled: true + protocols: + - http + - https +- name: rate-limiting-advanced + config: + namespace: silver + limit: + - 5 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 diff --git a/types/certificate.go b/types/certificate.go index 5349c7ad9..83811681b 100644 --- a/types/certificate.go +++ b/types/certificate.go @@ -12,7 +12,8 @@ import ( // certificateCRUD implements crud.Actions interface. type certificateCRUD struct { - client *kong.Client + client *kong.Client + isKonnect bool } func certificateFromStruct(arg crud.Event) *state.Certificate { @@ -30,6 +31,9 @@ func certificateFromStruct(arg crud.Event) *state.Certificate { func (s *certificateCRUD) Create(ctx context.Context, arg ...crud.Arg) (crud.Arg, error) { event := crud.EventFromArg(arg[0]) certificate := certificateFromStruct(event) + if s.isKonnect { + certificate.SNIs = nil + } createdCertificate, err := s.client.Certificates.Create(ctx, &certificate.Certificate) if err != nil { return nil, err @@ -59,6 +63,9 @@ func (s *certificateCRUD) Update(ctx context.Context, arg ...crud.Arg) (crud.Arg event := crud.EventFromArg(arg[0]) certificate := certificateFromStruct(event) + if s.isKonnect { + certificate.SNIs = nil + } updatedCertificate, err := s.client.Certificates.Create(ctx, &certificate.Certificate) if err != nil { return nil, err @@ -70,6 +77,8 @@ type certificateDiffer struct { kind crud.Kind currentState, targetState *state.KongState + + isKonnect bool } func (d *certificateDiffer) Deletes(handler func(crud.Event) error) error { @@ -138,6 +147,13 @@ func (d *certificateDiffer) createUpdateCertificate( certificateCopy := &state.Certificate{Certificate: *certificate.DeepCopy()} currentCertificate, err := d.currentState.Certificates.Get(*certificate.ID) + if d.isKonnect { + certificateCopy.SNIs = nil + if currentCertificate != nil { + currentCertificate.SNIs = nil + } + } + if errors.Is(err, state.ErrNotFound) { // certificate not present, create it return &crud.Event{ @@ -161,18 +177,20 @@ func (d *certificateDiffer) createUpdateCertificate( // To work around this issues, we set SNIs on certificates here using the // current certificate's SNI list. If there are changes to the SNIs, // subsequent actions on the SNI objects will handle those. - currentSNIs, err := d.currentState.SNIs.GetAllByCertID(*currentCertificate.ID) - if err != nil { - return nil, fmt.Errorf("error looking up current certificate SNIs %q: %w", - certificate.FriendlyName(), err) - } - sniNames := make([]*string, 0) - for _, s := range currentSNIs { - sniNames = append(sniNames, s.Name) - } + if !d.isKonnect { + currentSNIs, err := d.currentState.SNIs.GetAllByCertID(*currentCertificate.ID) + if err != nil { + return nil, fmt.Errorf("error looking up current certificate SNIs %q: %w", + certificate.FriendlyName(), err) + } + sniNames := make([]*string, 0) + for _, s := range currentSNIs { + sniNames = append(sniNames, s.Name) + } - certificateCopy.SNIs = sniNames - currentCertificate.SNIs = sniNames + certificateCopy.SNIs = sniNames + currentCertificate.SNIs = sniNames + } return &crud.Event{ Op: crud.Update, Kind: d.kind, diff --git a/types/consumer.go b/types/consumer.go index 65a1092d2..4d5b52c1c 100644 --- a/types/consumer.go +++ b/types/consumer.go @@ -95,7 +95,7 @@ func (d *consumerDiffer) Deletes(handler func(crud.Event) error) error { } func (d *consumerDiffer) deleteConsumer(consumer *state.Consumer) (*crud.Event, error) { - _, err := d.targetState.Consumers.Get(*consumer.ID) + _, err := d.targetState.Consumers.GetByIDOrUsername(*consumer.ID) if errors.Is(err, state.ErrNotFound) { return &crud.Event{ Op: crud.Delete, @@ -133,7 +133,7 @@ func (d *consumerDiffer) CreateAndUpdates(handler func(crud.Event) error) error func (d *consumerDiffer) createUpdateConsumer(consumer *state.Consumer) (*crud.Event, error) { consumerCopy := &state.Consumer{Consumer: *consumer.DeepCopy()} - currentConsumer, err := d.currentState.Consumers.Get(*consumer.ID) + currentConsumer, err := d.currentState.Consumers.GetByIDOrUsername(*consumer.ID) if errors.Is(err, state.ErrNotFound) { // consumer not present, create it @@ -181,12 +181,38 @@ func (d *consumerDiffer) DuplicatesDeletes() ([]crud.Event, error) { } func (d *consumerDiffer) deleteDuplicateConsumer(targetConsumer *state.Consumer) (*crud.Event, error) { - currentConsumer, err := d.currentState.Consumers.Get(*targetConsumer.Username) - if errors.Is(err, state.ErrNotFound) { - return nil, nil + var ( + idOrUsername string + + currentConsumer *state.Consumer + err error + ) + + if targetConsumer.Username != nil { + idOrUsername = *targetConsumer.Username + } else if targetConsumer.ID != nil { + idOrUsername = *targetConsumer.ID + } + + if idOrUsername != "" { + currentConsumer, err = d.currentState.Consumers.GetByIDOrUsername(idOrUsername) + } + if errors.Is(err, state.ErrNotFound) || idOrUsername == "" { + if targetConsumer.CustomID != nil { + currentConsumer, err = d.currentState.Consumers.GetByCustomID(*targetConsumer.CustomID) + if errors.Is(err, state.ErrNotFound) { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("error looking up consumer by custom_id %q: %w", + *targetConsumer.Username, err) + } + } else { + return nil, nil + } } if err != nil { - return nil, fmt.Errorf("error looking up consumer %q: %w", + return nil, fmt.Errorf("error looking up consumer by username or id %q: %w", *targetConsumer.Username, err) } diff --git a/types/core.go b/types/core.go index b914673bd..9d27a2283 100644 --- a/types/core.go +++ b/types/core.go @@ -336,7 +336,8 @@ func NewEntity(t EntityType, opts EntityOpts) (Entity, error) { return entityImpl{ typ: Certificate, crudActions: &certificateCRUD{ - client: opts.KongClient, + client: opts.KongClient, + isKonnect: opts.IsKonnect, }, postProcessActions: &certificatePostAction{ currentState: opts.CurrentState, @@ -345,6 +346,7 @@ func NewEntity(t EntityType, opts EntityOpts) (Entity, error) { kind: entityTypeToKind(Certificate), currentState: opts.CurrentState, targetState: opts.TargetState, + isKonnect: opts.IsKonnect, }, }, nil case CACertificate: diff --git a/types/plugin.go b/types/plugin.go index 44339c0f2..fc3a59525 100644 --- a/types/plugin.go +++ b/types/plugin.go @@ -26,6 +26,9 @@ func stripPluginReferencesName(plugin *state.Plugin) { if plugin.Plugin.Consumer != nil && plugin.Plugin.Consumer.Username != nil { plugin.Plugin.Consumer.Username = nil } + if plugin.Plugin.ConsumerGroup != nil && plugin.Plugin.ConsumerGroup.Name != nil { + plugin.Plugin.ConsumerGroup.Name = nil + } } func pluginFromStruct(arg crud.Event) *state.Plugin { @@ -111,9 +114,10 @@ func (d *pluginDiffer) Deletes(handler func(crud.Event) error) error { func (d *pluginDiffer) deletePlugin(plugin *state.Plugin) (*crud.Event, error) { plugin = &state.Plugin{Plugin: *plugin.DeepCopy()} name := *plugin.Name - serviceID, routeID, consumerID := foreignNames(plugin) - _, err := d.targetState.Plugins.GetByProp(name, serviceID, routeID, - consumerID) + serviceID, routeID, consumerID, consumerGroupID := foreignNames(plugin) + _, err := d.targetState.Plugins.GetByProp( + name, serviceID, routeID, consumerID, consumerGroupID, + ) if errors.Is(err, state.ErrNotFound) { return &crud.Event{ Op: crud.Delete, @@ -151,9 +155,10 @@ func (d *pluginDiffer) CreateAndUpdates(handler func(crud.Event) error) error { func (d *pluginDiffer) createUpdatePlugin(plugin *state.Plugin) (*crud.Event, error) { plugin = &state.Plugin{Plugin: *plugin.DeepCopy()} name := *plugin.Name - serviceID, routeID, consumerID := foreignNames(plugin) - currentPlugin, err := d.currentState.Plugins.GetByProp(name, - serviceID, routeID, consumerID) + serviceID, routeID, consumerID, consumerGroupID := foreignNames(plugin) + currentPlugin, err := d.currentState.Plugins.GetByProp( + name, serviceID, routeID, consumerID, consumerGroupID, + ) if errors.Is(err, state.ErrNotFound) { // plugin not present, create it @@ -181,7 +186,7 @@ func (d *pluginDiffer) createUpdatePlugin(plugin *state.Plugin) (*crud.Event, er return nil, nil } -func foreignNames(p *state.Plugin) (serviceID, routeID, consumerID string) { +func foreignNames(p *state.Plugin) (serviceID, routeID, consumerID, consumerGroupID string) { if p == nil { return } @@ -194,5 +199,8 @@ func foreignNames(p *state.Plugin) (serviceID, routeID, consumerID string) { if p.Consumer != nil && p.Consumer.ID != nil { consumerID = *p.Consumer.ID } + if p.ConsumerGroup != nil && p.ConsumerGroup.ID != nil { + consumerGroupID = *p.ConsumerGroup.ID + } return } diff --git a/utils/constants.go b/utils/constants.go index da25209bd..0176b6a55 100644 --- a/utils/constants.go +++ b/utils/constants.go @@ -21,7 +21,7 @@ var ( routeDefaults = kong.Route{ PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), - StripPath: kong.Bool(false), + StripPath: kong.Bool(true), Protocols: kong.StringSlice("http", "https"), } targetDefaults = kong.Target{ diff --git a/utils/defaulter_test.go b/utils/defaulter_test.go index c138d204f..9e25f9e62 100644 --- a/utils/defaulter_test.go +++ b/utils/defaulter_test.go @@ -180,7 +180,7 @@ func TestRouteSetTest(t *testing.T) { want: &kong.Route{ PreserveHost: kong.Bool(true), RegexPriority: kong.Int(0), - StripPath: kong.Bool(false), + StripPath: kong.Bool(true), Protocols: kong.StringSlice("http", "https"), }, }, @@ -192,7 +192,7 @@ func TestRouteSetTest(t *testing.T) { want: &kong.Route{ PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), - StripPath: kong.Bool(false), + StripPath: kong.Bool(true), Protocols: kong.StringSlice("http", "tls"), }, }, @@ -202,7 +202,7 @@ func TestRouteSetTest(t *testing.T) { Name: kong.String("foo"), Hosts: kong.StringSlice("1.example.com", "2.example.com"), Methods: kong.StringSlice("GET", "POST"), - StripPath: kong.Bool(false), + StripPath: kong.Bool(true), }, want: &kong.Route{ Name: kong.String("foo"), @@ -210,7 +210,7 @@ func TestRouteSetTest(t *testing.T) { Methods: kong.StringSlice("GET", "POST"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), - StripPath: kong.Bool(false), + StripPath: kong.Bool(true), Protocols: kong.StringSlice("http", "https"), }, }, diff --git a/utils/types.go b/utils/types.go index a4d377df1..8c5c32d70 100644 --- a/utils/types.go +++ b/utils/types.go @@ -81,6 +81,15 @@ func (e ErrArray) Error() string { return res } +func (e ErrArray) ErrorList() []string { + errList := []string{} + + for _, err := range e.Errors { + errList = append(errList, err.Error()) + } + return errList +} + // KongClientConfig holds config details to use to talk to a Kong server. type KongClientConfig struct { Address string @@ -292,6 +301,7 @@ func GetKonnectClient(httpClient *http.Client, config KonnectConfig) (*konnect.C if httpClient == nil { defaultTransport := http.DefaultTransport.(*http.Transport) + defaultTransport.Proxy = http.ProxyFromEnvironment httpClient = http.DefaultClient httpClient.Transport = defaultTransport } @@ -329,6 +339,7 @@ func HTTPClient() *http.Client { Timeout: clientTimeout, }).DialContext, TLSHandshakeTimeout: clientTimeout, + Proxy: http.ProxyFromEnvironment, }, } } diff --git a/utils/utils.go b/utils/utils.go index e00f37c6b..f87d034f3 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -2,6 +2,7 @@ package utils import ( "context" + "errors" "fmt" "net/url" "os" @@ -21,6 +22,14 @@ var ( Kong140Version = semver.MustParse("1.4.0") Kong300Version = semver.MustParse("3.0.0") + Kong340Version = semver.MustParse("3.4.0") +) + +var ErrorConsumerGroupUpgrade = errors.New( + "a rate-limiting-advanced plugin with config.consumer_groups\n" + + "and/or config.enforce_consumer_groups was found. Please use Consumer Groups scoped\n" + + "Plugins when running against Kong Enterprise 3.4.0 and above.\n\n" + + "Check https://docs.konghq.com/gateway/latest/kong-enterprise/consumer-groups/ for more information", ) var UpgradeMessage = "Please upgrade your configuration to account for 3.0\n" + @@ -148,6 +157,16 @@ func GetConsumerReference(c kong.Consumer) *kong.Consumer { return consumer } +// GetConsumerGroupReference returns a name+ID only copy of the input consumer-group, +// for use in references from other objects +func GetConsumerGroupReference(c kong.ConsumerGroup) *kong.ConsumerGroup { + consumerGroup := &kong.ConsumerGroup{ID: kong.String(*c.ID)} + if c.Name != nil { + consumerGroup.Name = kong.String(*c.Name) + } + return consumerGroup +} + // GetServiceReference returns a name+ID only copy of the input service, // for use in references from other objects func GetServiceReference(s kong.Service) *kong.Service {