pax_global_header00006660000000000000000000000064140060356370014516gustar00rootroot0000000000000052 comment=b906b43342dad0c62b16f611d6047d0c995f0012 deck-1.4.0/000077500000000000000000000000001400603563700124265ustar00rootroot00000000000000deck-1.4.0/.ci/000077500000000000000000000000001400603563700130775ustar00rootroot00000000000000deck-1.4.0/.ci/check.sh000077500000000000000000000002211400603563700145060ustar00rootroot00000000000000#!/bin/bash -ex diff -u <(echo -n) <(gofmt -d -s .) ./scripts/verify-codegen.sh golint -set_exit_status $(go list ./...) go vet . go test ./... deck-1.4.0/.dockerignore000066400000000000000000000000141400603563700150750ustar00rootroot00000000000000.git vendor deck-1.4.0/.editorconfig000066400000000000000000000000501400603563700150760ustar00rootroot00000000000000[*.go] end_of_line = lf indent_size = 2 deck-1.4.0/.github/000077500000000000000000000000001400603563700137665ustar00rootroot00000000000000deck-1.4.0/.github/workflows/000077500000000000000000000000001400603563700160235ustar00rootroot00000000000000deck-1.4.0/.github/workflows/master-mirrors-main.yaml000066400000000000000000000006261400603563700226230ustar00rootroot00000000000000name: 'sync master with main' on: push: branches: - 'main' jobs: push-master: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 with: fetch-depth: 0 - name: Push HEAD to master # Deliberately not force-pushing: designed to fail if this would mean rewriting history run: | git push origin HEAD:master deck-1.4.0/.github/workflows/test.yaml000066400000000000000000000011641400603563700176700ustar00rootroot00000000000000name: CI Test on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - name: Setup go uses: actions/setup-go@v2 with: go-version: '^1.15' - name: Checkout repository uses: actions/checkout@v2 - name: Setup golangci-lint run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.27.0 - name: Run golangci-lint run: golangci-lint run ./... - name: Verify Codegen run: ./scripts/verify-codegen.sh - name: Run tests run: go test ./... deck-1.4.0/.gitignore000066400000000000000000000004621400603563700144200ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test vendor # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof # for this repo only deck dist/ deck-1.4.0/.golangci.yml000066400000000000000000000013171400603563700150140ustar00rootroot00000000000000linters: enable: - megacheck - govet - bodyclose - unparam - unconvert - misspell - lll - gofmt - golint - gosec - nakedret - maligned - dogsled - depguard issues: exclude-rules: - linters: - staticcheck text: "SA1012" # do not pass a nil Context - linters: - gosec text: "weak cryptographic primitive" path: "state/indexers/md5Indexer.*" - linters: - gosec text: "weak random number generator" path: _test\.go - linters: - errcheck text: "Error return value" # ignore err not checked in test files path: _test\.go - linters: - gosec text: "Expect WriteFile permissions to be 0600 or less" path: file/codegen/main.go deck-1.4.0/.goreleaser.yml000066400000000000000000000016171400603563700153640ustar00rootroot00000000000000builds: - env: - CGO_ENABLED=0 goos: - linux - darwin - windows - freebsd goarch: - amd64 asmflags: - all=-trimpath={{.Env.GOPATH}} ldflags: - -s -w -X github.com/kong/deck/cmd.VERSION={{ .Tag }} -X github.com/kong/deck/cmd.COMMIT={{ .ShortCommit }} checksum: name_template: 'checksums.txt' snapshot: name_template: "{{ .Tag }}" nfpms: - file_name_template: '{{ .ProjectName }}_{{ .Tag }}_{{ .Arch }}' homepage: https://github.com/kong/deck description: Declarative configuration for Kong maintainer: Harry Bagdi license: Apache License v2.0 formats: - deb - rpm brews: - tap: owner: kong name: homebrew-deck commit_author: name: Harry Bagdi email: harrybagdi@gmail.com homepage: "https://github.com/kong/deck" description: Declarative configuration for Kong skip_upload: true test: | system "#{bin}/deck version" deck-1.4.0/CHANGELOG.md000066400000000000000000000522041400603563700142420ustar00rootroot00000000000000# Table of Contents - [v1.4.0](#v140---20210201) - [v1.3.0](#v130---20210115) - [v1.2.4](#v124---20210106) - [v1.2.3](#v123---20201118) - [v1.2.2](#v122---20201019) - [v1.2.1](#v121---20200804) - [v1.2.0](#v120---20200804) - [v1.1.0](#v110---20200405) - [v1.0.3](#v103---20200314) - [v1.0.2](#v102---20200221) - [v1.0.1](#v101---20200214) - [v1.0.0](#v100---20200118) - [v0.7.2](#v072---20191229) - [v0.7.1](#v071---20191224) - [v0.7.0](#v070---20191207) - [v0.6.2](#v062---20191116) - [v0.6.1](#v061---20191108) - [v0.6.0](#v060---20191103) - [v0.5.2](#v052---20190915) - [v0.5.1](#v051---20190824) - [v0.5.0](#v050---20190818) - [v0.4.0](#v040---20190610) - [v0.3.0](#v030---20190514) - [v0.2.0](#v020---20190401) - [v0.1.0](#v010---20190112) ## [v1.4.0] - 2021/02/01 ### Added - deck now handles the `request_buffering` and `response_buffering` options for `Route` [#261](https://github.com/Kong/deck/pull/261) ### Fixes - Updated brew syntax [#252](https://github.com/Kong/deck/pull/252) - Fixed YAML/JSON file detection logic [#255](https://github.com/Kong/deck/pull/255) ## [v1.3.0] - 2021/01/15 ### Added - decK will now retry sync operations that encounter a 500 error several times before failing completely. [#226](https://github.com/Kong/deck/pull/226) ### Fixed - Fixed regression that broke workspace creation. [#252](https://github.com/Kong/deck/pull/252) - Analytics failures no longer delay execution. [#254](https://github.com/Kong/deck/pull/254) ## [v1.2.4] - 2021/01/06 ### Fixed - Fixed a bug that disabled verbose output. [#243](https://github.com/Kong/deck/pull/243) - decK no longer considers tag order significant. This avoids unnecessary resource updates for Cassandra-backed clusters. [#240](https://github.com/Kong/deck/pull/240) ## [v1.2.3] - 2020/11/18 ### Fixed - Sync operations now handle plugins with array configuration correctly. [#229](https://github.com/Kong/deck/pull/229) - Removed unecessary permissions requirement for checking workspace existence. [#225](https://github.com/Kong/deck/pull/225) ## [v1.2.2] - 2020/10/19 ### Added - decK now prints a change summary even if it encountered an error. [#197](https://github.com/hbagdi/deck/pull/197) - decK now prints the ID of entities that it could not successfully sync. [#199](https://github.com/hbagdi/deck/pull/199) - Issues sending analytics will now emit a panic. [#200](https://github.com/hbagdi/deck/pull/200) - decK now creates the workspace specified with `--workspace` if it is not already present. [#201](https://github.com/hbagdi/deck/pull/201) - decK prints descriptive information about duplicated entities. [#204](https://github.com/hbagdi/deck/pull/204) ### Fixed - Resolved a concurrency bug during syncs. [#202](https://github.com/hbagdi/deck/pull/202) ## [v1.2.1] - 2020/08/04 ### Summary decK has move under Kong's umbrella. Due to this change, the package path has changed from `github.com/hbagdi/deck` to `github.com/kong/deck`. This release contains the updated `go.mod` over v1.2.0. There are no other changes introduced in this release. ## [v1.2.0] - 2020/08/04 ### Added - decK is now compatible with Kong 2.1: - New Admin API properties for entities are added. - Ordering of operations has changed to incorporate for new foreign-relations [#192](https://github.com/hbagdi/deck/pull/192) - New flag `--db-update-propagation-delay` to add an artifical delay between Admin API calls. This is introduced for better compatibility with Cassandra backed installations of Kong. [#160](https://github.com/hbagdi/deck/pull/160) [#154](https://github.com/hbagdi/deck/pull/154) - decK now errors out if there are invalid positional arguments supplied to any command. - Stricter validation of state files. [#162](https://github.com/hbagdi/deck/pull/162) - ID property of CACertificate is always exported. [#193](https://github.com/hbagdi/deck/pull/193) ### Fixed - Ignore error for missing `.deck` config file [#168](https://github.com/hbagdi/deck/pull/168) - Correctly populate port in Service's URL (a sugar attribute) [#166](https://github.com/hbagdi/deck/pull/166) - Correct the help text for `--tls-server-name` flag [#170](https://github.com/hbagdi/deck/pull/170) - Better sanitization of `--kong-addr` input [#171](https://github.com/hbagdi/deck/pull/171) - Fix typos in the output of `--help` [#174](https://github.com/hbagdi/deck/pull/174) - Improve language of warning message for basic-auth credentials [#145](https://github.com/hbagdi/deck/pull/145) - Deduplicate `select_tags` input [#183](https://github.com/hbagdi/deck/pull/183) ### Enterprise-only - Added support for managing `mtls-auth` credentials. [#175](https://github.com/hbagdi/deck/pull/175) - decK now automatically creates a workspace if one does not already exist during a `sync` operation. [#187](https://github.com/hbagdi/deck/pull/187) - Added `--workspace` flag to `ping` command. This can be used to verify connectivity with Kong Enterprise when running as an RBAC role with lower priviliges. - New `--workspace` flag for `diff` and `sync` command to provide workspace via the CLI instead of state file. Workspace defined in state file will be overriden if this flag is provided. - New `--skip-workspace-crud` flag to skip any workspace related operations. This flag can be used when running as as an RBAC role with lower priviliges. The content can be synced to specific workspaces but decK will not attempt to create or verify existence of a workspace. [#157](https://github.com/hbagdi/deck/pull/157) - Additional checks for existence of workspace before performing dump or reset [#167](https://github.com/hbagdi/deck/pull/167) - Improve end-user error message when workspace doesn't exist #### Misc - CI changed from Travis to Github Actions - Improved code quality with addition of golangci-lint - Default branch for the project has been changed from `master` to `main` ## [v1.1.0] - 2020/04/05 ### Added - Added support for multiple files or directories to `-s/--state` flag. Use `-s` multiple times or specify multiple files/directories using a comma separated list. [#137](https://github.com/hbagdi/deck/pull/137) - **Performance** decK should be much faster than before. Requests to Kong are now concurrent. `dump`, `sync`, `diff` and `reset` commands will be faster than before, by at least 2x. - SNI entity in Kong is not supported natively supported [#139](https://github.com/hbagdi/deck/pull/139). Most users will not observe any changes. `id` and `tags` are now supported for the SNI entity in Kong. ### Under the hood - Go has been upgraded to 1.14.1 - Alpine base image for Docker has been upgraded to 3.11 - Multiple other dependencies have also been upgraded, but these have no user-visible changes. ### Fixed - Default values for `retries` in Service entity and `HTTPSVerifyCertificate` in Upstream entity have been removed. These values can be set to `0` and `false` respectively now. [#134](https://github.com/hbagdi/deck/issues/134) ## [v1.0.3] - 2020/03/14 ### Fixed - Fix certificate diff for certificates with no associated snis [#131](https://github.com/hbagdi/deck/issues/131) ## [v1.0.2] - 2020/02/21 ### Fixed - Fix broken `ca_certificate` entity support [#127](https://github.com/hbagdi/deck/pull/127) ## [v1.0.1] - 2020/02/14 ### Added - decK now supports the `url` sugar property on Service entity. [#123](https://github.com/hbagdi/deck/issues/123) ## [v1.0.0] - 2020/01/18 ### Fixed - decK doesn't error out if bundled plugins in Kong are disabled [#121](https://github.com/hbagdi/deck/pull/121) - Consumer-specific plugins are excluded when `--skip-consumers` is used [#119](https://github.com/hbagdi/deck/issues/119) ### Internal - `go-kong` has been upgraded to v0.11.0, which brings in support for Kong 2.0. - All other dependencies have also been upgraded, but these have no user-visible changes. [b603f9](https://github.com/hbagdi/deck/commit/b603f9) ## [v0.7.2] - 2019/12/29 ### Fixed - Kong's version is correctly parsed; v0.7.1 is unusable because of this bug. [#117](https://github.com/hbagdi/deck/issues/117) ## [v0.7.1] - 2019/12/24 ### Fixed - Backward compatibility for credentials; tags are no longer injected into credentials for Kong versions below 1.4 [#114](https://github.com/hbagdi/deck/issues/114) ## [v0.7.0] - 2019/12/07 ### Breaking changes - `sync` command now shows the progress of the sync. Previously, the command did not output anything but errors. ### Added - Configuration of multiple plugin instances can now be de-duplicated using `_plugin_configs` field in the state file. [#93](https://github.com/hbagdi/deck/issues/93) - A summary is now presented at the end of a `diff` or `sync` operation showing the count of resources created/updated/deleted. [#101](https://github.com/hbagdi/deck/issues/101) - `sync` command now shows the progress of the sync as the sync takes place, making it easier to track progress in large environments. [#100](https://github.com/hbagdi/deck/issues/100) - `--non-zero-exit-code` flag hsa been added to `diff` command. Using this flag causes decK to exit with a non-zero exit code if a diff is detected, making it easier to script decK in CI pipelines. [#98](https://github.com/hbagdi/deck/issues/98) - A new docs website has been setup for the project: [https://deck.yolo42.com](https://deck.yolo42.com) ## [v0.6.2] - 2019/11/16 ### Fixed - Service-less routes are correctly processed [#103](https://github.com/hbagdi/deck/issues/103) - Plugins for routes are correctly processed [#104](https://github.com/hbagdi/deck/issues/104) ## [v0.6.1] - 2019/11/08 ### Fixed - Check for workspace makes call the right endpoint [#94](https://github.com/hbagdi/deck/issues/94) - Error checking is performed correctly when ensuring existence of a workspace [#95](https://github.com/hbagdi/deck/issues/95) - Multiple upstream definitions are read correctly and synced up [#96](https://github.com/hbagdi/deck/issues/96) ## [v0.6.0] - 2019/11/03 ### Breaking changes - `ID` field is required for `Certificate` entity. Previous state files will break if `ID` is not present on this entity. You can use `dump` command to generate new state files which includes the `ID` field. - SNIs are exported under the `name` key under Certificate entity to match Kong's declarative configuration format. ### Added - Kong's configuration can now be synced/diffed/dumped using JSON format, in addition to the existing YAML format. Use the `--format` flag to specify the format. [#35](https://github.com/hbagdi/deck/issues/35) - Plugins associated with multiple entities e.g. a plugin for a combination of route and a consumer in Kong are now supported. [#13](https://github.com/hbagdi/deck/issues/13) - JSON-schema based validation is now performed on the input file(s) for every command. - New `validate` command has been added to validate an existing state file. This performs a JSON-schema based sanity check on the file along-with foreign reference checks to check for dangling pointers. - Service-less routes are now supported by decK. - `name` is no longer a required field for routes and services entities in Kong. If a `name` is not present, decK exports the entity with it's `ID`. - Client-certificates on Service entity are now a supported. - Credential entities like key-auth, basic-auth now support tagging. - `--parallelism` flag has been added to `sync` and `diff` commands to control the number of concurrenty request to Kong's Admin API. [#85](https://github.com/hbagdi/deck/issues/85) - `diff` and `sync` show a descriptive error when a workspace doesn't exist for Kong Enterprise. [102ed5dd](https://github.com/hbagdi/deck/commit/102ed5dd6f8ef) - `--select-tag` flag has been added to `diff` and `sync` command for use-cases where the tags are not part of the state file. It is not recommended to use these flags unless you know what you are doing. [#81](https://github.com/hbagdi/deck/issues/81) - ID for any entity can now be specified. decK previously ignored the ID for any entity if one was specified. Entities can also be exported with the `ID` field set using `--with-id` flag on the `dump` command. [#29](https://github.com/hbagdi/deck/issues/29) ### Fixed - decK runs as non-root user in the Docker image. [#82](https://github.com/hbagdi/deck/issues/82) - SNIs are now exported same as Kong's format i.e. they are exported under a `name` key under the certificates entity. [#76](https://github.com/hbagdi/deck/issues/76) - Errors are made more descriptive in few commands. - decK's binary inside the Docker image now contains versioning information. [#38](https://github.com/hbagdi/deck/issues/38) ### Internal - Go has been bumped up to `1.13.4`. - `go-kong` has been bumped up to `v0.10.0`. - Reduced memory allocation, which should result in less GC pressure. ## [v0.5.2] - 2019/09/15 ### Added - `-w/--workspace` flag has been added to the `reset` command to reset a specific workspace in Kong Enterprise. [#74](https://github.com/hbagdi/deck/issues/74) - `--all-workspaces` flag has been added to the `reset` command to reset all workspaces in Kong Enterprise. [#74](https://github.com/hbagdi/deck/issues/74) - A warning is logged when basic-auth credentials are being synced. [#49](https://github.com/hbagdi/deck/issues/49) ### Fixed - Kong Enterprise Developer Portal exposes the credentials (basic/key) of Developers on the Admin API, but doesn't expose the consumers causing issues during export. decK now ignores these credentials in Kong Enterprise. [#75](https://github.com/hbagdi/deck/issues/75) ### Internal - Go version has been bumped to 1.13. ## [v0.5.1] - 2019/08/24 ### Added - `oauth2` credentials associated with consumers are now supported. [#67](https://github.com/hbagdi/deck/pull/67) ### Fixed - The same target can be associated with multiple upstreams. [#57](https://github.com/hbagdi/deck/issues/57) - Fix compatibility with Kong < 1.3. [#59](https://github.com/hbagdi/deck/issues/59) - Ignore credentials for consumers which are not in the sub-set of the configuration being synced. [#65](https://github.com/hbagdi/deck/issues/65) ## [v0.5.0] - 2019/08/18 ### Summary This release brings the following features: - Consumer credentials are now supported - Support for Kong 1.3 - Kong Enterprise workspace support - Reading configuration from multiple files in a directories ### Breaking changes No breaking changes have been introduced in this release. ### Added - **Consumer credentials** The following entities associate with a consumer in Kong are now supported [#12](https://github.com/hbagdi/deck/issues/12): - `key-auth` - `basic-auth` - `hmac-auth` - `jwt` - `acl` - decK's exported YAML is now compatible with Kong's declarative config file. - **Homebrew support** decK can now be installed using Homebrew on macOS: ``` brew tap hbagdi/deck brew install deck ``` - **Multiple state files** decK can now read the configuration of Kong from multiple YAML files in a directory. You can split your configuration into files in any way you would like. [#22](https://github.com/hbagdi/deck/issues/22) - Upcoming Kong 1.3 is now supported. [#36](https://github.com/hbagdi/deck/issues/36) - **Kong Enterprise only features:** Workspaces are now natively supported in decK - `-w/--workspace` flag can be specified in the `dump` command to export configuration of a single workspace. - `--all-workspaces` flag in `dump` command will export all workspaces in Kong Enteprise. Each workspace lives in a separate state file. - `diff` and `sync` command now support workspaces via the `_workspace` attribute in the state file. ### Fixed - decK now supports TCP services in Kong. [#44](https://github.com/hbagdi/deck/issues/44) - Add missing `interval` field in Upstream entity's unhealthy active healthchecks [#45](https://github.com/hbagdi/deck/pull/45) - Docker image now contains only the binary and not the entire source code. [#34](https://github.com/hbagdi/deck/pull/34) Thanks to [David Cruz](https://github.com/davidcv5) for the contribution. ## [v0.4.0] - 2019/06/10 ### Summary This release introduces support for Kong 1.2.x. ### Breaking changes - `strip_path` attribute of Route can now be set to false. The default value is now false, which was true previously. [#18](https://github.com/hbagdi/deck/issues/18) ### Added - `https_redirect_status_code` attribute of Route in Kong can be set, and defaults to `426`. ## [v0.3.0] - 2019/05/14 ### Breaking changes No breaking changes have been introduced in this release. ### Added - **Tag-based distributed configuration management** Only a subset of Kong entities sharing a (set of) tag can now be exported, deleted, diffed or synced. decK can now manage your Kong's configuration in a distributed manner, whereby you can split Kong's configuration by team and each team can manage it's own configuration. Use `select-tag` feature in all the commands and config file for this purpose. [#17](https://github.com/hbagdi/deck/pull/17) - **Read/write state from stdout/stdin** Config file can now be read in from standard-input and written out to standard-output. [#10](https://github.com/hbagdi/deck/pull/10), [#11](https://github.com/hbagdi/deck/pull/11) Thanks to [@matthewbednarski](https://github.com/matthewbednarski) for the contribution. - **Automated defaults** No need to specify default values for all core Kong entities, further simplifying your Kong's configuration. Default values for plugin configuration still need to be defined, this is on the roadmap. [b448d4f](https://github.com/hbagdi/deck/commit/b448d4f) - Add support for new properties in Upstream entity in Kong. [080200d](https://github.com/hbagdi/deck/commit/080200d) - Empty plugins and other Kong entities are not populated in the config file as empty arrays to keep the file concise and clean. [ae38f1b](https://github.com/hbagdi/deck/commit/ae38f1b) - Docker image is now available via Docker Hub. You can use `docker pull hbagdi/deck` to pull down decK in a Docker image. ### Fixed - Empty arrays in plugin configs are not treated as nil anymore. [#9](https://github.com/hbagdi/deck/pull/9) - Correctly sync plugins which are out of sync. Protocols field in plugins can be confused with protocols field in routes in Kong [#6](https://github.com/hbagdi/deck/pull/6) Thanks to [@davidcv5](https://github.com/davidcv5) for the contribution. - Throw an error if an object is not marshalled into YAML correctly. - Correctly create service-level plugins for Kong >= 1.1 [#16](https://github.com/hbagdi/deck/pull/16) ### Misc - `go-kong` has been bumped up to v0.4.1. ## [v0.2.0] - 2019/04/01 ### Breaking changes No breaking changes have been introduced in this release. ### Added - **Consumers and consumer-level plugins** can now be exported from Kong and synced to Kong. - `--skip-consumers` flag has been introduced to various sub-commands to skip management of consumers in environments where they are created dynamically.` - **Authentication support**: custom HTTP Headers (key:value) can be injected into requests that decK makes to Kong's Admin API using the `--headers` CLI flag. [#1](https://github.com/hbagdi/deck/pull/1) Thanks to [@davidcv5](https://github.com/davidcv5) for the contribution. ### Fixed - Infinite loop in pagination for exporting entities in Kong [#2](https://github.com/hbagdi/deck/pull/2) Thanks to [@lmika](https://github.com/lmika) for the contribution. - Plugins are updated using PUT requests instead of PATCH to avoid any schema violations. ## [v0.1.0] - 2019/01/12 ### Summary Debut release of decK [v1.4.0]: https://github.com/kong/deck/compare/v1.3.0...v1.4.0 [v1.3.0]: https://github.com/kong/deck/compare/v1.2.4...v1.3.0 [v1.2.4]: https://github.com/kong/deck/compare/v1.2.3...v1.2.4 [v1.2.3]: https://github.com/kong/deck/compare/v1.2.2...v1.2.3 [v1.2.2]: https://github.com/kong/deck/compare/v1.2.1...v1.2.2 [v1.2.1]: https://github.com/hbagdi/deck/compare/v1.2.0...v1.2.1 [v1.2.0]: https://github.com/hbagdi/deck/compare/v1.1.0...v1.2.0 [v1.1.0]: https://github.com/hbagdi/deck/compare/v1.0.3...v1.1.0 [v1.0.3]: https://github.com/hbagdi/deck/compare/v1.0.2...v1.0.3 [v1.0.2]: https://github.com/hbagdi/deck/compare/v1.0.1...v1.0.2 [v1.0.1]: https://github.com/hbagdi/deck/compare/v1.0.0...v1.0.1 [v1.0.0]: https://github.com/hbagdi/deck/compare/v0.7.2...v1.0.0 [v0.7.2]: https://github.com/hbagdi/deck/compare/v0.7.1...v0.7.2 [v0.7.1]: https://github.com/hbagdi/deck/compare/v0.7.0...v0.7.1 [v0.7.0]: https://github.com/hbagdi/deck/compare/v0.6.2...v0.7.0 [v0.6.2]: https://github.com/hbagdi/deck/compare/v0.6.1...v0.6.2 [v0.6.1]: https://github.com/hbagdi/deck/compare/v0.6.0...v0.6.1 [v0.6.0]: https://github.com/hbagdi/deck/compare/v0.5.2...v0.6.0 [v0.5.2]: https://github.com/hbagdi/deck/compare/v0.5.1...v0.5.2 [v0.5.1]: https://github.com/hbagdi/deck/compare/v0.5.0...v0.5.1 [v0.5.0]: https://github.com/hbagdi/deck/compare/v0.4.0...v0.5.0 [v0.4.0]: https://github.com/hbagdi/deck/compare/v0.3.0...v0.4.0 [v0.3.0]: https://github.com/hbagdi/deck/compare/v0.2.0...v0.3.0 [v0.2.0]: https://github.com/hbagdi/deck/compare/v0.1.0...v0.2.0 [v0.1.0]: https://github.com/hbagdi/deck/compare/0c7e839...v0.1.0 deck-1.4.0/Dockerfile000066400000000000000000000007321400603563700144220ustar00rootroot00000000000000FROM golang:1.15.2 AS build WORKDIR /deck COPY go.mod ./ COPY go.sum ./ RUN go mod download ADD . . ARG COMMIT 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.11 RUN adduser --disabled-password --gecos "" deckuser RUN apk --no-cache add ca-certificates USER deckuser COPY --from=build /deck/deck /usr/local/bin ENTRYPOINT ["deck"] deck-1.4.0/LICENSE000066400000000000000000000261641400603563700134440ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2018-2020 Harry Bagdi Copyright 2020-2020 Kong Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. deck-1.4.0/README.md000066400000000000000000000070311400603563700137060ustar00rootroot00000000000000# decK: Declarative configuration for Kong decK provides declarative configuration and drift detection for Kong. [![Build Status](https://github.com/kong/deck/workflows/CI%20Test/badge.svg)](https://github.com/kong/deck/actions?query=branch%3Amain+event%3Apush) [![asciicast](https://asciinema.org/a/238318.svg)](https://asciinema.org/a/238318) ## Table of Content - [**Features**](#features) - [**Compatibility**](#compatibility) - [**Installation**](#installation) - [**Documentation**](#documentation) - [**Stale issue and pull request policy**](#stale-issue-and-pull-request-policy) - [**License**](#license) ## Features - **Export** Existing Kong configuration to a YAML configuration file This can be used to backup Kong's configuration. - **Import** Kong's database can be populated using the exported or a hand written config file. - **Diff and sync capabilities** decK can diff the configuration in the config file and the configuration in Kong's DB and then sync it as well. This can be used to detect config drifts or manual interventions. - **Reverse sync** decK supports a sync the other way as well, meaning if an entity is created in Kong and doesn't add it to the config file, decK will detect the change. - **Validation** decK can validate a YAML file that you backup or modify to catch errors early on. - **Reset** This can be used to drops all entities in Kong's DB. - **Parallel operations** All Admin API calls to Kong are executed in parallel using multiple threads to speed up the sync process. - **Authentication with Kong** Custom HTTP headers can be injected in requests to Kong's Admin API for authentication/authorization purposes. - **Manage Kong's config with multiple config file** Split your Kong's configuration into multiple logical files based on a shared set of tags amongst entities. - **Designed to automate configuration management** decK is designed to be part of your CI pipeline and can be used to not only push configuration to Kong but also detect drifts in configuration. ## Compatibility decK is compatible with Kong Gateway >= 1.x and Kong Enterprise >= 0.35. ## Installation ### macOS If you are on macOS, install decK using brew: ```shell $ brew tap kong/deck $ brew install deck ``` ### Linux If you are Linux, you can either use the Debian or RPM archive from the Github [release page](https://github.com/kong/deck/releases) or install by downloading the binary: ```shel $ curl -sL https://github.com/kong/deck/releases/download/v1.2.0/deck_1.2.0_linux_amd64.tar.gz -o deck.tar.gz $ tar -xf deck.tar.gz -C /tmp $ sudo cp /tmp/deck /usr/local/bin/ ``` ### Docker image Docker image is hosted on [Docker Hub](https://hub.docker.com/r/kong/deck). You can get the image with the command: ``` docker pull kong/deck ``` ## Documentation You can use `--help` flag once you've decK installed on your system to get help in the terminal itself. The project's documentation is hosted at [https://docs.konghq.com/deck/overview](https://docs.konghq.com/deck/overview). ## Changelog Changelog can be found in the [CHANGELOG.md](CHANGELOG.md) file. ## Stale issue and pull request policy To ensure our backlog is organized and up to date, we will close issues and pull requests that have been inactive awaiting a community response for over 2 weeks. If you wish to reopen a closed issue or PR to continue work, please leave a comment asking a team member to do so. ## License decK is licensed with Apache License Version 2.0. Please read the [LICENSE](LICENSE) file for more details. deck-1.4.0/cmd/000077500000000000000000000000001400603563700131715ustar00rootroot00000000000000deck-1.4.0/cmd/analytics.go000066400000000000000000000011271400603563700155100ustar00rootroot00000000000000package cmd import ( "context" "net/http" "os" ) func sendAnalytics(ctx context.Context) { const ( minOSArgs = 2 ) if os.Getenv("DECK_ANALYTICS") == "off" { return } if len(os.Args) < minOSArgs { return } cmd := os.Args[1] if cmd == "help" || cmd == "ping" || cmd == "version" { return } // HTTP to avoid latency due to handshake URL := "http://d.yolo42.com/" + cmd req, _ := http.NewRequestWithContext(ctx, "GET", URL, nil) req.Header["deck-version"] = []string{VERSION} resp, err := http.DefaultClient.Do(req) if err != nil { return } resp.Body.Close() } deck-1.4.0/cmd/common.go000066400000000000000000000120001400603563700150010ustar00rootroot00000000000000package cmd import ( "context" "net/http" "os" "github.com/blang/semver" "github.com/fatih/color" "github.com/kong/deck/diff" "github.com/kong/deck/dump" "github.com/kong/deck/file" "github.com/kong/deck/print" "github.com/kong/deck/solver" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" "github.com/pkg/errors" "github.com/spf13/cobra" ) const ( exitCodeDiffDetection = 2 ) var ( stopChannel chan struct{} dumpConfig dump.Config ) // SetStopCh sets the stop channel for long running commands. // This is useful for cases when a process needs to be cancelled gracefully // before it can complete to finish. Example: SIGINT func SetStopCh(stopCh chan struct{}) { stopChannel = stopCh } // workspaceExists checks if workspace exists in Kong. func workspaceExists(config utils.KongClientConfig) (bool, error) { if config.Workspace == "" { // default workspace always exists return true, nil } if config.SkipWorkspaceCrud { // if RBAC user, skip check return true, nil } wsClient, err := utils.GetKongClient(config) if err != nil { return false, err } _, _, err = wsClient.Routes.List(context.TODO(), nil) switch { case kong.IsNotFoundErr(err): return false, nil case err == nil: return true, nil default: return false, errors.Wrap(err, "checking if workspace exists") } } func syncMain(filenames []string, dry bool, parallelism, delay int, workspace string) error { // read target file targetContent, err := file.GetContentFromFiles(filenames) if err != nil { return err } rootClient, err := utils.GetKongClient(rootConfig) if err != nil { return err } var wsConfig utils.KongClientConfig // prepare to read the current state from Kong if workspace != targetContent.Workspace && workspace != "" { print.DeletePrintf("Warning: Workspace '%v' specified via --workspace flag is "+ "different from workspace '%v' found in state file(s).\n", workspace, targetContent.Workspace) wsConfig = rootConfig.ForWorkspace(workspace) } else { wsConfig = rootConfig.ForWorkspace(targetContent.Workspace) } // load Kong version after workspace kongVersion, err := kongVersion(wsConfig) if err != nil { return errors.Wrap(err, "reading Kong version") } workspaceExists, err := workspaceExists(wsConfig) if err != nil { return err } wsClient, err := utils.GetKongClient(wsConfig) if err != nil { return err } if targetContent.Info != nil { dumpConfig.SelectorTags = targetContent.Info.SelectorTags } // read the current state var currentState *state.KongState if workspaceExists { rawState, err := dump.Get(wsClient, dumpConfig) if err != nil { return err } currentState, err = state.Get(rawState) if err != nil { return err } } else { print.CreatePrintln("creating workspace", wsConfig.Workspace) // inject empty state currentState, err = state.NewKongState() if err != nil { return err } if !dry { _, err = rootClient.Workspaces.Create(nil, &kong.Workspace{Name: &wsConfig.Workspace}) if err != nil { return err } } } // read the target state rawState, err := file.Get(targetContent, file.RenderConfig{ CurrentState: currentState, KongVersion: kongVersion, }) if err != nil { return err } targetState, err := state.Get(rawState) if err != nil { return err } s, _ := diff.NewSyncer(currentState, targetState) s.StageDelaySec = delay stats, errs := solver.Solve(stopChannel, s, wsClient, parallelism, dry) printFn := color.New(color.FgGreen, color.Bold).PrintfFunc() printFn("Summary:\n") printFn(" Created: %v\n", stats.CreateOps) printFn(" Updated: %v\n", stats.UpdateOps) printFn(" Deleted: %v\n", stats.DeleteOps) if errs != nil { return utils.ErrArray{Errors: errs} } if diffCmdNonZeroExitCode && stats.CreateOps+stats.UpdateOps+stats.DeleteOps != 0 { os.Exit(exitCodeDiffDetection) } return nil } func kongVersion(config utils.KongClientConfig) (semver.Version, error) { var version string workspace := config.Workspace // remove workspace to be able to call top-level / endpoint config.Workspace = "" client, err := utils.GetKongClient(config) if err != nil { return semver.Version{}, err } root, err := client.Root(nil) if err != nil { if workspace == "" { return semver.Version{}, err } // try with workspace path req, err := http.NewRequest("GET", utils.CleanAddress(config.Address)+"/"+workspace+"/kong", nil) if err != nil { return semver.Version{}, err } var resp map[string]interface{} _, err = client.Do(nil, req, &resp) if err != nil { return semver.Version{}, err } version = resp["version"].(string) } else { version = root["version"].(string) } v, err := utils.CleanKongVersion(version) if err != nil { return semver.Version{}, err } return semver.ParseTolerant(v) } func validateNoArgs(cmd *cobra.Command, args []string) error { if len(args) > 0 { return errors.New("positional arguments are not valid for this command, please use flags instead\n" + "Run 'deck --help' for usage.") } return nil } deck-1.4.0/cmd/diff.go000066400000000000000000000042101400603563700144250ustar00rootroot00000000000000package cmd import ( "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( diffCmdKongStateFile []string diffCmdParallelism int diffCmdNonZeroExitCode bool diffWorkspace string ) // diffCmd represents the diff command var diffCmd = &cobra.Command{ Use: "diff", Short: "Diff the current entities in Kong with the on on disks", Long: `Diff is like a dry run of 'decK sync' command. It will load entities form Kong and then perform a diff on those with the entities present in files locally. This allows you to see the entities that will be created or updated or deleted. `, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { return syncMain(diffCmdKongStateFile, true, diffCmdParallelism, 0, diffWorkspace) }, PreRunE: func(cmd *cobra.Command, args []string) error { if len(diffCmdKongStateFile) == 0 { return errors.New("A state file with Kong's configuration " + "must be specified using -s/--state flag.") } return nil }, } func init() { rootCmd.AddCommand(diffCmd) diffCmd.Flags().StringSliceVarP(&diffCmdKongStateFile, "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ "This flag can be specified multiple times for multiple files.\n"+ "Use '-' to read from stdin.") diffCmd.Flags().StringVarP(&diffWorkspace, "workspace", "w", "", "Diff configuration with a specific workspace "+ "(Kong Enterprise only).\n"+ "This takes precedence over _workspace fields in state files.") diffCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", false, "do not diff consumers or "+ "any plugins associated with consumers") diffCmd.Flags().IntVar(&diffCmdParallelism, "parallelism", 10, "Maximum number of concurrent operations") diffCmd.Flags().StringSliceVar(&dumpConfig.SelectorTags, "select-tag", []string{}, "only entities matching tags specified via this flag are diffed.\n"+ "Multiple tags are ANDed together.") diffCmd.Flags().BoolVar(&diffCmdNonZeroExitCode, "non-zero-exit-code", false, "return exit code 2 if there is a diff present,\n"+ "exit code 0 if no diff is found,\n"+ "and exit code 1 if an error occurs.") } deck-1.4.0/cmd/dump.go000066400000000000000000000110401400603563700144610ustar00rootroot00000000000000package cmd import ( "net/http" "strings" "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" "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( dumpCmdKongStateFile string dumpCmdStateFormat string dumpWorkspace string dumpAllWorkspaces bool dumpWithID bool ) func listWorkspaces(client *kong.Client, baseURL string) ([]string, error) { type Workspace struct { Name string } type Response struct { Data []Workspace } var response Response // TODO handle pagination req, err := http.NewRequest("GET", baseURL+"/workspaces?size=1000", nil) if err != nil { return nil, errors.Wrap(err, "building request for fetching workspaces") } _, err = client.Do(nil, req, &response) if err != nil { return nil, errors.Wrap(err, "fetching workspaces from Kong") } var res []string for _, workspace := range response.Data { res = append(res, workspace.Name) } return res, nil } // dumpCmd represents the dump command var dumpCmd = &cobra.Command{ Use: "dump", Short: "Export Kong configuration to a file", Long: `Dump command reads all the entities present in Kong and writes them to a file on disk. The file can then be read using the Sync o Diff command to again configure Kong.`, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { wsClient, err := utils.GetKongClient(rootConfig) if err != nil { return err } format := file.Format(strings.ToUpper(dumpCmdStateFormat)) // Kong Enterprise dump all workspace if dumpAllWorkspaces { if dumpWorkspace != "" { return errors.New("workspace cannot be specified with --all-workspace flag") } if dumpCmdKongStateFile != "kong" { return errors.New("output-file cannot be specified with --all-workspace flag") } workspaces, err := listWorkspaces(wsClient, rootConfig.Address) if err != nil { return err } for _, workspace := range workspaces { wsClient, err := utils.GetKongClient(rootConfig.ForWorkspace(workspace)) if err != nil { return err } rawState, err := dump.Get(wsClient, dumpConfig) if err != nil { return errors.Wrap(err, "reading configuration from Kong") } ks, err := state.Get(rawState) if err != nil { return errors.Wrap(err, "building state") } if err := file.KongStateToFile(ks, file.WriteConfig{ SelectTags: dumpConfig.SelectorTags, Workspace: workspace, Filename: workspace, FileFormat: format, WithID: dumpWithID, }); err != nil { return err } } return nil } // Kong OSS // or Kong Enterprise single workspace if dumpWorkspace != "" { wsConfig := rootConfig.ForWorkspace(dumpWorkspace) exists, err := workspaceExists(wsConfig) if err != nil { return err } if !exists { return errors.Errorf("workspace '%v' does not exist in Kong", dumpWorkspace) } wsClient, err = utils.GetKongClient(wsConfig) if err != nil { return err } } rawState, err := dump.Get(wsClient, dumpConfig) if err != nil { return errors.Wrap(err, "reading configuration from Kong") } ks, err := state.Get(rawState) if err != nil { return errors.Wrap(err, "building state") } if err := file.KongStateToFile(ks, file.WriteConfig{ SelectTags: dumpConfig.SelectorTags, Workspace: dumpWorkspace, Filename: dumpCmdKongStateFile, FileFormat: format, WithID: dumpWithID, }); err != nil { return err } return nil }, } func init() { rootCmd.AddCommand(dumpCmd) dumpCmd.Flags().StringVarP(&dumpCmdKongStateFile, "output-file", "o", "kong", "file to which to write Kong's configuration."+ "Use '-' to write to stdout.") dumpCmd.Flags().StringVar(&dumpCmdStateFormat, "format", "yaml", "output file format: json or yaml") dumpCmd.Flags().BoolVar(&dumpWithID, "with-id", false, "write ID of all entities in the output") dumpCmd.Flags().StringVarP(&dumpWorkspace, "workspace", "w", "", "dump configuration of a specific workspace"+ "(Kong Enterprise only).") dumpCmd.Flags().BoolVar(&dumpAllWorkspaces, "all-workspaces", false, "dump configuration of all workspaces (Kong Enterprise only).") dumpCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", false, "skip exporting consumers and any plugins associated "+ "with consumers") dumpCmd.Flags().StringSliceVar(&dumpConfig.SelectorTags, "select-tag", []string{}, "only entities matching tags specified via this flag are exported.\n"+ "Multiple tags are ANDed together.") } deck-1.4.0/cmd/ping.go000066400000000000000000000016611400603563700144610ustar00rootroot00000000000000package cmd import ( "fmt" "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( pingWorkspace string ) // pingCmd represents the ping command var pingCmd = &cobra.Command{ Use: "ping", Short: "Verify connectivity with Kong", Long: `Ping command can be used to verify if decK can connect to Kong's Admin API or not.`, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { wsConfig := rootConfig.ForWorkspace(pingWorkspace) version, err := kongVersion(wsConfig) if err != nil { return errors.Wrap(err, "reading Kong version") } fmt.Println("Successfully connected to Kong!") fmt.Println("Kong version: ", version) return nil }, } func init() { rootCmd.AddCommand(pingCmd) pingCmd.Flags().StringVarP(&pingWorkspace, "workspace", "w", "", "Ping configuration with a specific workspace "+ "(Kong Enterprise only).\n"+ "Useful when RBAC permissions are scoped to a workspace.") } deck-1.4.0/cmd/reset.go000066400000000000000000000066731400603563700146560ustar00rootroot00000000000000package cmd import ( "fmt" "strings" "github.com/kong/deck/dump" "github.com/kong/deck/reset" "github.com/kong/deck/utils" "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( resetCmdForce bool resetWorkspace string resetAllWorkspaces bool ) // resetCmd represents the reset command var resetCmd = &cobra.Command{ Use: "reset", Short: "Reset deletes all entities in Kong", Long: `Reset command will delete all entities in Kong's database.string Use this command with extreme care as it is equivalent to running "kong migrations reset" on your Kong instance. By default, this command will ask for a confirmation prompt.`, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { if !resetCmdForce { ok, err := confirm() if err != nil { return err } if !ok { return nil } } rootClient, err := utils.GetKongClient(rootConfig) if err != nil { return err } // Kong OSS or default workspace if !resetAllWorkspaces && resetWorkspace == "" { state, err := dump.Get(rootClient, dumpConfig) if err != nil { return err } err = reset.Reset(state, rootClient) if err != nil { return err } return nil } if resetAllWorkspaces && resetWorkspace != "" { return errors.New("workspace cannot be specified with --all-workspace flag") } // Kong Enterprise var workspaces []string if resetAllWorkspaces { workspaces, err = listWorkspaces(rootClient, rootConfig.Address) if err != nil { return err } } if resetWorkspace != "" { exists, err := workspaceExists(rootConfig.ForWorkspace(resetWorkspace)) if err != nil { return err } if !exists { return errors.Errorf("workspace '%v' does not exist in Kong", resetWorkspace) } workspaces = append(workspaces, resetWorkspace) } for _, workspace := range workspaces { wsClient, err := utils.GetKongClient(rootConfig.ForWorkspace(workspace)) if err != nil { return err } state, err := dump.Get(wsClient, dumpConfig) if err != nil { return err } err = reset.Reset(state, wsClient) if err != nil { return err } } return nil }, } // confirm prompts a user for a confirmation // and returns true with no error if input is "yes" or "y" (case-insensitive), // otherwise false. func confirm() (bool, error) { fmt.Println("This will delete all configuration from Kong's database.") fmt.Print("> Are you sure? ") yes := []string{"yes", "y"} var input string _, err := fmt.Scanln(&input) if err != nil { return false, err } input = strings.ToLower(input) for _, valid := range yes { if input == valid { return true, nil } } return false, nil } func init() { rootCmd.AddCommand(resetCmd) resetCmd.Flags().BoolVarP(&resetCmdForce, "force", "f", false, "Skip interactive confirmation prompt before reset") resetCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", false, "do not reset consumers or "+ "any plugins associated with consumers") resetCmd.Flags().StringVarP(&resetWorkspace, "workspace", "w", "", "reset configuration of a specific workspace"+ "(Kong Enterprise only).") resetCmd.Flags().BoolVar(&resetAllWorkspaces, "all-workspaces", false, "reset configuration of all workspaces (Kong Enterprise only).") resetCmd.Flags().StringSliceVar(&dumpConfig.SelectorTags, "select-tag", []string{}, "only entities matching tags specified via this flag are deleted.\n"+ "Multiple tags are ANDed together.") } deck-1.4.0/cmd/root.go000066400000000000000000000114521400603563700145060ustar00rootroot00000000000000package cmd import ( "context" "fmt" "net/url" "os" "strings" "sync" "github.com/fatih/color" "github.com/kong/deck/utils" homedir "github.com/mitchellh/go-homedir" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" ) var ( cfgFile string rootConfig utils.KongClientConfig ) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "deck", Short: "Administer your Kong declaratively", Long: `decK helps you manage Kong clusters with a declarative configuration file. It can be used to export, import or sync entities to Kong.`, SilenceUsage: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if _, err := url.ParseRequestURI(rootConfig.Address); err != nil { return errors.WithStack(errors.Wrap(err, "invalid URL")) } return nil }, } // Execute adds all child commands to the root command and sets // sflags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { var wg sync.WaitGroup var err error const threads = 2 wg.Add(threads) ctx, cancel := context.WithCancel(context.Background()) go func() { defer wg.Done() sendAnalytics(ctx) }() go func() { defer wg.Done() defer cancel() err = rootCmd.Execute() }() wg.Wait() if err != nil { os.Exit(1) } } //nolint:errcheck func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.deck.yaml)") rootCmd.PersistentFlags().String("kong-addr", "http://localhost:8001", "HTTP Address of Kong's Admin API.\n"+ "This value can also be set using DECK_KONG_ADDR\n"+ " environment variable.") viper.BindPFlag("kong-addr", rootCmd.PersistentFlags().Lookup("kong-addr")) rootCmd.PersistentFlags().StringSlice("headers", []string{}, "HTTP Headers(key:value) to inject in all requests to Kong's Admin API.\n"+ "This flag can be specified multiple times to inject multiple headers.") viper.BindPFlag("headers", rootCmd.PersistentFlags().Lookup("headers")) rootCmd.PersistentFlags().Bool("tls-skip-verify", false, "Disable verification of Kong's Admin TLS certificate.\n"+ "This value can also be set using DECK_TLS_SKIP_VERIFY "+ "environment variable.") viper.BindPFlag("tls-skip-verify", rootCmd.PersistentFlags().Lookup("tls-skip-verify")) rootCmd.PersistentFlags().String("tls-server-name", "", "Name to use to verify the hostname in "+ "Kong's Admin TLS certificate.\n"+ "This value can also be set using DECK_TLS_SERVER_NAME"+ " environment variable.") viper.BindPFlag("tls-server-name", rootCmd.PersistentFlags().Lookup("tls-server-name")) rootCmd.PersistentFlags().String("ca-cert", "", "Custom CA certificate to use to verify "+ "Kong's Admin TLS certificate.\n"+ "This value can also be set using DECK_CA_CERT"+ " environment variable.") viper.BindPFlag("ca-cert", rootCmd.PersistentFlags().Lookup("ca-cert")) rootCmd.PersistentFlags().Int("verbose", 0, "Enable verbose verbose logging levels\n"+ "Setting this value to 2 outputs all HTTP requests/responses\n"+ "between decK and Kong.") viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose")) rootCmd.PersistentFlags().Bool("no-color", false, "disable colorized output") viper.BindPFlag("no-color", rootCmd.PersistentFlags().Lookup("no-color")) rootCmd.PersistentFlags().Bool("skip-workspace-crud", false, "Skip API calls related to Workspaces (Kong Enterprise only)") viper.BindPFlag("skip-workspace-crud", rootCmd.PersistentFlags().Lookup("skip-workspace-crud")) } // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != "" { // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { // Find home directory. home, err := homedir.Dir() if err != nil { fmt.Println(err) os.Exit(1) } // Search config in home directory with name ".deck"(without extension). viper.AddConfigPath(home) viper.SetConfigName(".deck") } viper.SetEnvPrefix("deck") viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { fmt.Println(err) } } rootConfig.Address = viper.GetString("kong-addr") rootConfig.TLSServerName = viper.GetString("tls-server-name") rootConfig.TLSSkipVerify = viper.GetBool("tls-skip-verify") rootConfig.TLSCACert = viper.GetString("ca-cert") rootConfig.Headers = viper.GetStringSlice("headers") rootConfig.SkipWorkspaceCrud = viper.GetBool("skip-workspace-crud") rootConfig.Debug = (viper.GetInt("verbose") >= 1) color.NoColor = (color.NoColor || viper.GetBool("no-color")) } deck-1.4.0/cmd/sync.go000066400000000000000000000041461400603563700145010ustar00rootroot00000000000000package cmd import ( "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( syncCmdKongStateFile []string syncCmdParallelism int syncCmdDBUpdateDelay int syncWorkspace string ) // syncCmd represents the sync command var syncCmd = &cobra.Command{ Use: "sync", Short: "Sync performs operations to get Kong's configuration " + "to match the state file", Long: `Sync command reads the state file and performs operation on Kong to get Kong's state in sync with the input state.`, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { return syncMain(syncCmdKongStateFile, false, syncCmdParallelism, syncCmdDBUpdateDelay, syncWorkspace) }, PreRunE: func(cmd *cobra.Command, args []string) error { if len(syncCmdKongStateFile) == 0 { return errors.New("A state file with Kong's configuration " + "must be specified using -s/--state flag.") } return nil }, } func init() { rootCmd.AddCommand(syncCmd) syncCmd.Flags().StringSliceVarP(&syncCmdKongStateFile, "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ "This flag can be specified multiple times for multiple files.\n"+ "Use '-' to read from stdin.") syncCmd.Flags().StringVar(&syncWorkspace, "workspace", "", "Sync configuration to a specific workspace "+ "(Kong Enterprise only).\n"+ "This takes precedence over _workspace fields in state files.") syncCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", false, "do not diff consumers or "+ "any plugins associated with consumers") syncCmd.Flags().IntVar(&syncCmdParallelism, "parallelism", 10, "Maximum number of concurrent operations") syncCmd.Flags().StringSliceVar(&dumpConfig.SelectorTags, "select-tag", []string{}, "only entities matching tags specified via this flag are synced.\n"+ "Multiple tags are ANDed together.") syncCmd.Flags().IntVar(&syncCmdDBUpdateDelay, "db-update-propagation-delay", 0, "aritificial delay in seconds that is injected between insert operations \n"+ "for related entities (usually for cassandra deployments).\n"+ "See 'db_update_propagation' in kong.conf.") } deck-1.4.0/cmd/validate.go000066400000000000000000000034571400603563700153220ustar00rootroot00000000000000package cmd import ( "github.com/kong/deck/file" "github.com/kong/deck/state" "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( validateCmdKongStateFile []string ) // validateCmd represents the diff command var validateCmd = &cobra.Command{ Use: "validate", Short: "Validate the state file", Long: `Validate reads the state file and ensures the validity. It will read all the state files that are passed in. If there are YAML/JSON parsing issues, they will be reported. It also checks for foreign relationships and alerts if there are broken relationships, missing links present. No communication takes places between decK and Kong during the execution of this command. `, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { // read target file // this does json schema validation as well targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile) if err != nil { return err } dummyEmptyState, err := state.NewKongState() if err != nil { return err } rawState, err := file.Get(targetContent, file.RenderConfig{ CurrentState: dummyEmptyState, }) if err != nil { return err } // this catches foreign relation errors _, err = state.Get(rawState) if err != nil { return err } return nil }, PreRunE: func(cmd *cobra.Command, args []string) error { if len(validateCmdKongStateFile) == 0 { return errors.New("A state file with Kong's configuration " + "must be specified using -s/--state flag.") } return nil }, } func init() { rootCmd.AddCommand(validateCmd) validateCmd.Flags().StringSliceVarP(&validateCmdKongStateFile, "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ "This flag can be specified multiple times for multiple files.\n"+ "Use '-' to read from stdin.") } deck-1.4.0/cmd/version.go000066400000000000000000000013501400603563700152040ustar00rootroot00000000000000package cmd import ( "fmt" "github.com/spf13/cobra" ) // VERSION is the current version of decK. // This should be substituted by git tag during the build process. var VERSION = "dev" // COMMIT is the short hash of the source tree. // This should be substituted by Git commit hash during the build process. var COMMIT = "unknown" // versionCmd represents the version command var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version of decK", Long: `version prints the version of decK along with git short commit hash of the source tree`, Args: validateNoArgs, Run: func(cmd *cobra.Command, args []string) { fmt.Printf("decK %s (%s) \n", VERSION, COMMIT) }, } func init() { rootCmd.AddCommand(versionCmd) } deck-1.4.0/crud/000077500000000000000000000000001400603563700133635ustar00rootroot00000000000000deck-1.4.0/crud/registry.go000066400000000000000000000056321400603563700155700ustar00rootroot00000000000000package crud import "github.com/pkg/errors" // Kind represents Kind of an entity or object. type Kind string // Registry can hold Kinds and their respective CRUD operations. type Registry struct { types map[Kind]Actions } func (r *Registry) typesMap() map[Kind]Actions { if r.types == nil { r.types = make(map[Kind]Actions) } return r.types } // Register a kind with actions. // An error will be returned if kind was previously registered. func (r *Registry) Register(kind Kind, a Actions) error { if kind == "" { return errors.New("kind cannot be empty") } m := r.typesMap() if _, ok := m[kind]; ok { return errors.New("kind '" + string(kind) + "' already registered") } m[kind] = a return nil } // MustRegister is same as Register but panics on error. func (r *Registry) MustRegister(kind Kind, a Actions) { err := r.Register(kind, a) if err != nil { panic(err) } } // Get returns actions associated with kind. // An error will be returned if kind was never registered. func (r *Registry) Get(kind Kind) (Actions, error) { if kind == "" { return nil, errors.New("kind cannot be empty") } m := r.typesMap() a, ok := m[kind] if !ok { return nil, errors.New("kind '" + string(kind) + "' is not registered") } return a, nil } // Create calls the registered create action of kind with args // and returns the result and error (if any). func (r *Registry) Create(kind Kind, args ...Arg) (Arg, error) { a, err := r.Get(kind) if err != nil { return nil, errors.Wrap(err, "create failed") } res, err := a.Create(args...) if err != nil { return nil, errors.Wrap(err, "create failed") } return res, nil } // Update calls the registered update action of kind with args // and returns the result and error (if any). func (r *Registry) Update(kind Kind, args ...Arg) (Arg, error) { a, err := r.Get(kind) if err != nil { return nil, errors.Wrap(err, "update failed") } res, err := a.Update(args...) if err != nil { return nil, errors.Wrap(err, "update failed") } return res, nil } // Delete calls the registered delete action of kind with args // and returns the result and error (if any). func (r *Registry) Delete(kind Kind, args ...Arg) (Arg, error) { a, err := r.Get(kind) if err != nil { return nil, errors.Wrap(err, "delete failed") } res, err := a.Delete(args...) if err != nil { return nil, errors.Wrap(err, "delete failed") } return res, nil } // Do calls an aciton based on op with args and returns the result and error. func (r *Registry) Do(kind Kind, op Op, args ...Arg) (Arg, error) { a, err := r.Get(kind) if err != nil { return nil, errors.Wrapf(err, "%v failed", op) } var res Arg switch op.name { case Create.name: res, err = a.Create(args...) case Update.name: res, err = a.Update(args...) case Delete.name: res, err = a.Delete(args...) default: return nil, errors.New("unknown operation: " + op.name) } if err != nil { return nil, err } return res, nil } deck-1.4.0/crud/registry_test.go000066400000000000000000000110471400603563700166240ustar00rootroot00000000000000package crud import ( "testing" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) type testActionFixture struct { state string } func newTestActionFixture(state string) testActionFixture { return testActionFixture{state: state} } func (t testActionFixture) invoke(op string, inputs ...Arg) (Arg, error) { res := t.state + " " + op for _, input := range inputs { iString, ok := input.(string) if !ok { return nil, errors.New("input is not a string") } res += " " + iString } return res, nil } func (t testActionFixture) Create(input ...Arg) (Arg, error) { return t.invoke("create", input...) } func (t testActionFixture) Delete(input ...Arg) (Arg, error) { return t.invoke("delete", input...) } func (t testActionFixture) Update(input ...Arg) (Arg, error) { return t.invoke("update", input...) } func TestRegistryRegister(t *testing.T) { assert := assert.New(t) var r Registry var a Actions = newTestActionFixture("yolo") err := r.Register("", nil) assert.NotNil(err) err = r.Register("foo", a) assert.Nil(err) err = r.Register("foo", a) assert.NotNil(err) } func TestRegistryMustRegister(t *testing.T) { assert := assert.New(t) var r Registry var a Actions = newTestActionFixture("yolo") assert.Panics(func() { r.MustRegister("", nil) }) assert.NotPanics(func() { r.MustRegister("foo", a) }) assert.Panics(func() { r.MustRegister("foo", a) }) } func TestRegistryGet(t *testing.T) { assert := assert.New(t) var r Registry var a Actions = newTestActionFixture("foo") err := r.Register("foo", a) assert.Nil(err) a, err = r.Get("foo") assert.Nil(err) assert.NotNil(a) a, err = r.Get("bar") assert.NotNil(err) assert.Nil(a) a, err = r.Get("") assert.NotNil(err) assert.Nil(a) } func TestRegistryCreate(t *testing.T) { assert := assert.New(t) var r Registry var a Actions = newTestActionFixture("foo") err := r.Register("foo", a) assert.Nil(err) res, err := r.Create("foo", "yolo") assert.Nil(err) assert.NotNil(res) result, ok := res.(string) assert.True(ok) assert.Equal("foo create yolo", result) // make sure it takes multiple arguments res, err = r.Create("foo", "yolo", "always") assert.Nil(err) assert.NotNil(res) result, ok = res.(string) assert.True(ok) assert.Equal("foo create yolo always", result) res, err = r.Create("foo", 42) assert.NotNil(err) assert.Nil(res) res, err = r.Create("bar", 42) assert.NotNil(err) assert.Nil(res) } func TestRegistryUpdate(t *testing.T) { assert := assert.New(t) var r Registry var a Actions = newTestActionFixture("foo") err := r.Register("foo", a) assert.Nil(err) res, err := r.Update("foo", "yolo") assert.Nil(err) assert.NotNil(res) result, ok := res.(string) assert.True(ok) assert.Equal("foo update yolo", result) // make sure it takes multiple arguments res, err = r.Update("foo", "yolo", "always") assert.Nil(err) assert.NotNil(res) result, ok = res.(string) assert.True(ok) assert.Equal("foo update yolo always", result) res, err = r.Update("foo", 42) assert.NotNil(err) assert.Nil(res) res, err = r.Update("bar", 42) assert.NotNil(err) assert.Nil(res) } func TestRegistryDelete(t *testing.T) { assert := assert.New(t) var r Registry var a Actions = newTestActionFixture("foo") err := r.Register("foo", a) assert.Nil(err) res, err := r.Delete("foo", "yolo") assert.Nil(err) assert.NotNil(res) result, ok := res.(string) assert.True(ok) assert.Equal("foo delete yolo", result) // make sure it takes multiple arguments res, err = r.Delete("foo", "yolo", "always") assert.Nil(err) assert.NotNil(res) result, ok = res.(string) assert.True(ok) assert.Equal("foo delete yolo always", result) res, err = r.Delete("foo", 42) assert.NotNil(err) assert.Nil(res) res, err = r.Delete("bar", 42) assert.NotNil(err) assert.Nil(res) } func TestRegistryDo(t *testing.T) { assert := assert.New(t) var r Registry var a Actions = newTestActionFixture("foo") err := r.Register("foo", a) assert.Nil(err) res, err := r.Do("foo", Create, "yolo") assert.Nil(err) assert.NotNil(res) result, ok := res.(string) assert.True(ok) assert.Equal("foo create yolo", result) // make sure it takes multiple arguments res, err = r.Do("foo", Update, "yolo", "always") assert.Nil(err) assert.NotNil(res) result, ok = res.(string) assert.True(ok) assert.Equal("foo update yolo always", result) res, err = r.Do("foo", Delete, 42) assert.NotNil(err) assert.Nil(res) res, err = r.Do("foo", Op{"unknown-op"}, 42) assert.NotNil(err) assert.Nil(res) res, err = r.Do("bar", Create, "yolo") assert.NotNil(err) assert.Nil(res) } deck-1.4.0/crud/types.go000066400000000000000000000011371400603563700150600ustar00rootroot00000000000000package crud // Op represents type Op struct { name string } func (op *Op) String() string { return op.name } var ( // Create is a constant representing create operations. Create = Op{"Create"} // Update is a constant representing update operations. Update = Op{"Update"} // Delete is a constant representing delete operations. Delete = Op{"Delete"} ) // Arg is an argument to a callback function. type Arg interface{} // Actions is an interface for CRUD operations on any entity type Actions interface { Create(...Arg) (Arg, error) Delete(...Arg) (Arg, error) Update(...Arg) (Arg, error) } deck-1.4.0/crud/types_test.go000066400000000000000000000003501400603563700161130ustar00rootroot00000000000000package crud import ( "testing" "github.com/stretchr/testify/assert" ) func TestOpString(t *testing.T) { assert := assert.New(t) op := Op{"foo"} var op2 Op assert.Equal("foo", op.String()) assert.Equal("", op2.String()) } deck-1.4.0/diff/000077500000000000000000000000001400603563700133365ustar00rootroot00000000000000deck-1.4.0/diff/aclGroup.go000066400000000000000000000040761400603563700154500ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteACLGroups() error { currentACLGroups, err := sc.currentState.ACLGroups.GetAll() if err != nil { return errors.Wrap(err, "error fetching acls from state") } for _, aclGroup := range currentACLGroups { n, err := sc.deleteACLGroup(aclGroup) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteACLGroup(aclGroup *state.ACLGroup) (*Event, error) { // lookup by consumerID and Group _, err := sc.targetState.ACLGroups.Get(*aclGroup.Consumer.ID, *aclGroup.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "acl-group", Obj: aclGroup, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up acl '%v'", *aclGroup.Group) } return nil, nil } func (sc *Syncer) createUpdateACLGroups() error { targetACLGroups, err := sc.targetState.ACLGroups.GetAll() if err != nil { return errors.Wrap(err, "error fetching acls from state") } for _, aclGroup := range targetACLGroups { n, err := sc.createUpdateACLGroup(aclGroup) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateACLGroup(aclGroup *state.ACLGroup) (*Event, error) { aclGroup = &state.ACLGroup{ACLGroup: *aclGroup.DeepCopy()} currentACLGroup, err := sc.currentState.ACLGroups.Get( *aclGroup.Consumer.ID, *aclGroup.ID) if err == state.ErrNotFound { // aclGroup not present, create it return &Event{ Op: crud.Create, Kind: "acl-group", Obj: aclGroup, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up acl %v", *aclGroup.Group) } // found, check if update needed if !currentACLGroup.EqualWithOpts(aclGroup, false, true, false) { return &Event{ Op: crud.Update, Kind: "acl-group", Obj: aclGroup, OldObj: currentACLGroup, }, nil } return nil, nil } deck-1.4.0/diff/basicAuth.go000066400000000000000000000046721400603563700156010ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/print" "github.com/kong/deck/state" "github.com/pkg/errors" ) const ( basicAuthPasswordWarning = "Warning: import/export of basic-auth" + "credentials using decK doesn't work due to hashing of passwords in Kong." ) func (sc *Syncer) warnBasicAuth() { sc.once.Do(func() { if sc.SilenceWarnings { return } print.UpdatePrintln(basicAuthPasswordWarning) }) } func (sc *Syncer) deleteBasicAuths() error { currentBasicAuths, err := sc.currentState.BasicAuths.GetAll() if err != nil { return errors.Wrap(err, "error fetching basic-auths from state") } for _, basicAuth := range currentBasicAuths { n, err := sc.deleteBasicAuth(basicAuth) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteBasicAuth(basicAuth *state.BasicAuth) (*Event, error) { sc.warnBasicAuth() _, err := sc.targetState.BasicAuths.Get(*basicAuth.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "basic-auth", Obj: basicAuth, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up basic-auth '%v'", *basicAuth.Username) } return nil, nil } func (sc *Syncer) createUpdateBasicAuths() error { targetBasicAuths, err := sc.targetState.BasicAuths.GetAll() if err != nil { return errors.Wrap(err, "error fetching basic-auths from state") } for _, basicAuth := range targetBasicAuths { n, err := sc.createUpdateBasicAuth(basicAuth) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateBasicAuth(basicAuth *state.BasicAuth) (*Event, error) { sc.warnBasicAuth() basicAuth = &state.BasicAuth{BasicAuth: *basicAuth.DeepCopy()} currentBasicAuth, err := sc.currentState.BasicAuths.Get(*basicAuth.ID) if err == state.ErrNotFound { // basicAuth not present, create it return &Event{ Op: crud.Create, Kind: "basic-auth", Obj: basicAuth, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up basic-auth %v", *basicAuth.Username) } // found, check if update needed if !currentBasicAuth.EqualWithOpts(basicAuth, false, true, true, false) { return &Event{ Op: crud.Update, Kind: "basic-auth", Obj: basicAuth, OldObj: currentBasicAuth, }, nil } return nil, nil } deck-1.4.0/diff/caCert.go000066400000000000000000000042011400603563700150630ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteCACertificates() error { currentCACertificates, err := sc.currentState.CACertificates.GetAll() if err != nil { return errors.Wrap(err, "error fetching caCertificates from state") } for _, certificate := range currentCACertificates { n, err := sc.deleteCACertificate(certificate) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteCACertificate( caCert *state.CACertificate) (*Event, error) { _, err := sc.targetState.CACertificates.Get(*caCert.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "ca_certificate", Obj: caCert, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up caCertificate '%v'", caCert.Identifier()) } return nil, nil } func (sc *Syncer) createUpdateCACertificates() error { targetCACertificates, err := sc.targetState.CACertificates.GetAll() if err != nil { return errors.Wrap(err, "error fetching caCertificates from state") } for _, caCert := range targetCACertificates { n, err := sc.createUpdateCACertificate(caCert) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateCACertificate( caCert *state.CACertificate) (*Event, error) { caCertCopy := &state.CACertificate{CACertificate: *caCert.DeepCopy()} currentCACert, err := sc.currentState.CACertificates.Get(*caCert.ID) if err == state.ErrNotFound { // caCertificate not present, create it return &Event{ Op: crud.Create, Kind: "ca_certificate", Obj: caCertCopy, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up caCertificate %v", caCert.Identifier()) } // found, check if update needed if !currentCACert.EqualWithOpts(caCertCopy, false, true) { return &Event{ Op: crud.Update, Kind: "ca_certificate", Obj: caCertCopy, OldObj: currentCACert, }, nil } return nil, nil } deck-1.4.0/diff/cert.go000066400000000000000000000042371400603563700146300ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteCertificates() error { currentCertificates, err := sc.currentState.Certificates.GetAll() if err != nil { return errors.Wrap(err, "error fetching certificates from state") } for _, certificate := range currentCertificates { n, err := sc.deleteCertificate(certificate) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteCertificate( certificate *state.Certificate) (*Event, error) { _, err := sc.targetState.Certificates.Get(*certificate.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "certificate", Obj: certificate, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up certificate '%v'", certificate.Identifier()) } return nil, nil } func (sc *Syncer) createUpdateCertificates() error { targetCertificates, err := sc.targetState.Certificates.GetAll() if err != nil { return errors.Wrap(err, "error fetching certificates from state") } for _, certificate := range targetCertificates { n, err := sc.createUpdateCertificate(certificate) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateCertificate( certificate *state.Certificate) (*Event, error) { certificateCopy := &state.Certificate{Certificate: *certificate.DeepCopy()} currentCertificate, err := sc.currentState.Certificates.Get(*certificate.ID) if err == state.ErrNotFound { // certificate not present, create it return &Event{ Op: crud.Create, Kind: "certificate", Obj: certificateCopy, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up certificate %v", certificate.Identifier()) } // found, check if update needed if !currentCertificate.EqualWithOpts(certificateCopy, false, true) { return &Event{ Op: crud.Update, Kind: "certificate", Obj: certificateCopy, OldObj: currentCertificate, }, nil } return nil, nil } deck-1.4.0/diff/consumer.go000066400000000000000000000040251400603563700155210ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteConsumers() error { currentConsumers, err := sc.currentState.Consumers.GetAll() if err != nil { return errors.Wrap(err, "error fetching consumers from state") } for _, consumer := range currentConsumers { n, err := sc.deleteConsumer(consumer) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteConsumer(consumer *state.Consumer) (*Event, error) { _, err := sc.targetState.Consumers.Get(*consumer.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "consumer", Obj: consumer, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up consumer '%v'", consumer.Identifier()) } return nil, nil } func (sc *Syncer) createUpdateConsumers() error { targetConsumers, err := sc.targetState.Consumers.GetAll() if err != nil { return errors.Wrap(err, "error fetching consumers from state") } for _, consumer := range targetConsumers { n, err := sc.createUpdateConsumer(consumer) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateConsumer(consumer *state.Consumer) (*Event, error) { consumerCopy := &state.Consumer{Consumer: *consumer.DeepCopy()} currentConsumer, err := sc.currentState.Consumers.Get(*consumer.ID) if err == state.ErrNotFound { // consumer not present, create it return &Event{ Op: crud.Create, Kind: "consumer", Obj: consumerCopy, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up consumer %v", consumer.Identifier()) } // found, check if update needed if !currentConsumer.EqualWithOpts(consumerCopy, false, true) { return &Event{ Op: crud.Update, Kind: "consumer", Obj: consumerCopy, OldObj: currentConsumer, }, nil } return nil, nil } deck-1.4.0/diff/diff.go000066400000000000000000000220011400603563700145700ustar00rootroot00000000000000package diff import ( "net/http" "sync" "sync/atomic" "time" "github.com/cenkalti/backoff/v4" "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" "github.com/pkg/errors" ) var ( errEnqueueFailed = errors.New("failed to queue event") ) func defaultBackOff() backoff.BackOff { // For various reasons, Kong can temporarily fail to process // a valid request (e.g. when the database is under heavy load). // We retry each request up to 3 times on failure, after around // 1 second, 3 seconds, and 9 seconds (randomized exponential backoff). exponentialBackoff := backoff.NewExponentialBackOff() exponentialBackoff.InitialInterval = 1 * time.Second exponentialBackoff.Multiplier = 3 return backoff.WithMaxRetries(exponentialBackoff, 4) } // TODO get rid of the syncer struct and simply have a func for it // Syncer takes in a current and target state of Kong, // diffs them, generating a Graph to get Kong from current // to target state. type Syncer struct { currentState *state.KongState targetState *state.KongState postProcess crud.Registry eventChan chan Event errChan chan error stopChan chan struct{} inFlightOps int32 SilenceWarnings bool StageDelaySec int once sync.Once } // NewSyncer constructs a Syncer. func NewSyncer(current, target *state.KongState) (*Syncer, error) { s := &Syncer{} s.currentState, s.targetState = current, target s.postProcess.MustRegister("service", &servicePostAction{current}) s.postProcess.MustRegister("route", &routePostAction{current}) s.postProcess.MustRegister("upstream", &upstreamPostAction{current}) s.postProcess.MustRegister("target", &targetPostAction{current}) s.postProcess.MustRegister("certificate", &certificatePostAction{current}) s.postProcess.MustRegister("sni", &sniPostAction{current}) s.postProcess.MustRegister("ca_certificate", &caCertificatePostAction{current}) s.postProcess.MustRegister("plugin", &pluginPostAction{current}) s.postProcess.MustRegister("consumer", &consumerPostAction{current}) s.postProcess.MustRegister("key-auth", &keyAuthPostAction{current}) s.postProcess.MustRegister("hmac-auth", &hmacAuthPostAction{current}) s.postProcess.MustRegister("jwt-auth", &jwtAuthPostAction{current}) s.postProcess.MustRegister("basic-auth", &basicAuthPostAction{current}) s.postProcess.MustRegister("acl-group", &aclGroupPostAction{current}) s.postProcess.MustRegister("oauth2-cred", &oauth2CredPostAction{current}) s.postProcess.MustRegister("mtls-auth", &mtlsAuthPostAction{current}) return s, nil } func (sc *Syncer) diff() error { var err error err = sc.createUpdate() if err != nil { return err } err = sc.delete() if err != nil { return err } return nil } func (sc *Syncer) delete() error { var err error err = sc.deletePlugins() if err != nil { return err } err = sc.deleteKeyAuths() if err != nil { return err } err = sc.deleteHMACAuths() if err != nil { return err } err = sc.deleteJWTAuths() if err != nil { return err } err = sc.deleteBasicAuths() if err != nil { return err } err = sc.deleteOauth2Creds() if err != nil { return err } err = sc.deleteACLGroups() if err != nil { return err } err = sc.deleteMTLSAuths() if err != nil { return err } err = sc.deleteTargets() if err != nil { return err } err = sc.deleteSNIs() if err != nil { return err } // barrier for foreign relations // plugins must be deleted before services, routes and consumers // routes must be deleted before service can be deleted // credentials must be deleted before consumers // targets must be deleted before upstream // PLEASE NOTE that if the order is not preserved, then decK will error // out because deleting a child entity whose parent is already deleted // will return a 404 sc.wait() err = sc.deleteRoutes() if err != nil { return err } err = sc.deleteConsumers() if err != nil { return err } err = sc.deleteUpstreams() if err != nil { return err } // barrier for foreign relations // routes must be deleted before services sc.wait() err = sc.deleteServices() if err != nil { return err } // barrier for foreign relations // services must be deleted before certificates (client_certificate) sc.wait() err = sc.deleteCertificates() if err != nil { return err } // services must be deleted before ca_certificates err = sc.deleteCACertificates() if err != nil { return err } // finish delete before returning sc.wait() return nil } func (sc *Syncer) createUpdate() error { // TODO write an interface and register by types, // then execute in a particular order err := sc.createUpdateCertificates() if err != nil { return err } err = sc.createUpdateCACertificates() if err != nil { return err } err = sc.createUpdateConsumers() if err != nil { return err } err = sc.createUpdateUpstreams() if err != nil { return err } // barrier for foreign relations // upstreams must be created before targets // certificates must be created before SNIs // consumers must be created before creds of all kinds // certificates must be created before services (client_certificate) sc.wait() err = sc.createUpdateTargets() if err != nil { return err } err = sc.createUpdateSNIs() if err != nil { return err } err = sc.createUpdateServices() if err != nil { return err } err = sc.createUpdateKeyAuths() if err != nil { return err } err = sc.createUpdateHMACAuths() if err != nil { return err } err = sc.createUpdateJWTAuths() if err != nil { return err } err = sc.createUpdateBasicAuths() if err != nil { return err } err = sc.createUpdateOauth2Creds() if err != nil { return err } err = sc.createUpdateACLGroups() if err != nil { return err } err = sc.createUpdateMTLSAuths() if err != nil { return err } // barrier for foreign relations // services must be created before routes sc.wait() err = sc.createUpdateRoutes() if err != nil { return err } // barrier for foreign relations // services, routes and consumers must be created before plugins sc.wait() err = sc.createUpdatePlugins() if err != nil { return err } // finish createUpdate before returning sc.wait() return nil } func (sc *Syncer) queueEvent(e Event) error { atomic.AddInt32(&sc.inFlightOps, 1) select { case sc.eventChan <- e: return nil case <-sc.stopChan: atomic.AddInt32(&sc.inFlightOps, -1) return errEnqueueFailed } } func (sc *Syncer) eventCompleted() { atomic.AddInt32(&sc.inFlightOps, -1) } func (sc *Syncer) wait() { time.Sleep(time.Duration(sc.StageDelaySec) * time.Second) for atomic.LoadInt32(&sc.inFlightOps) != 0 { select { case <-sc.stopChan: return default: time.Sleep(1 * time.Millisecond) } } } // Run starts a diff and invokes d for every diff. func (sc *Syncer) Run(done <-chan struct{}, parallelism int, d Do) []error { if parallelism < 1 { return append([]error{}, errors.New("parallelism can not be negative")) } var wg sync.WaitGroup const eventBuffer = 10 sc.eventChan = make(chan Event, eventBuffer) sc.stopChan = make(chan struct{}) sc.errChan = make(chan error) // run rabbit run // start the consumers wg.Add(parallelism) for i := 0; i < parallelism; i++ { go func() { err := sc.eventLoop(d) if err != nil { sc.errChan <- err } wg.Done() }() } // start the producer wg.Add(1) go func() { err := sc.diff() if err != nil { sc.errChan <- err } close(sc.eventChan) wg.Done() }() // close the error chan once all done go func() { wg.Wait() close(sc.errChan) }() var errs []error select { case <-done: case err, ok := <-sc.errChan: if ok && err != nil { if err != errEnqueueFailed { errs = append(errs, err) } } } // stop the producer close(sc.stopChan) // collect errors for err := range sc.errChan { if err != errEnqueueFailed { errs = append(errs, err) } } return errs } // Do is the worker function to sync the diff // TODO remove crud.Arg type Do func(a Event) (crud.Arg, error) func (sc *Syncer) eventLoop(d Do) error { for event := range sc.eventChan { // Stop if program is terminated select { case <-sc.stopChan: return nil default: } err := sc.handleEvent(d, event) sc.eventCompleted() if err != nil { return err } } return nil } func (sc *Syncer) handleEvent(d Do, event Event) error { err := backoff.Retry(func() error { res, err := d(event) if err != nil { err = errors.Wrapf(err, "while processing event") var kongAPIError *kong.APIError if errors.As(err, &kongAPIError) && kongAPIError.Code() == http.StatusInternalServerError { // Only retry if the request to Kong returned a 500 status code return err } // Do not retry on other status codes return backoff.Permanent(err) } if res == nil { // Do not retry empty responses return backoff.Permanent(errors.New("result of event is nil")) } _, err = sc.postProcess.Do(event.Kind, event.Op, res) if err != nil { // Do not retry program errors return backoff.Permanent(errors.Wrap(err, "while post processing event")) } return nil }, defaultBackOff()) return err } deck-1.4.0/diff/hmacAuth.go000066400000000000000000000040141400603563700154160ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteHMACAuths() error { currentHMACAuths, err := sc.currentState.HMACAuths.GetAll() if err != nil { return errors.Wrap(err, "error fetching hmac-auths from state") } for _, hmacAuth := range currentHMACAuths { n, err := sc.deleteHMACAuth(hmacAuth) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteHMACAuth(hmacAuth *state.HMACAuth) (*Event, error) { _, err := sc.targetState.HMACAuths.Get(*hmacAuth.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "hmac-auth", Obj: hmacAuth, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up hmac-auth '%v'", *hmacAuth.Username) } return nil, nil } func (sc *Syncer) createUpdateHMACAuths() error { targetHMACAuths, err := sc.targetState.HMACAuths.GetAll() if err != nil { return errors.Wrap(err, "error fetching hmac-auths from state") } for _, hmacAuth := range targetHMACAuths { n, err := sc.createUpdateHMACAuth(hmacAuth) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateHMACAuth(hmacAuth *state.HMACAuth) (*Event, error) { hmacAuth = &state.HMACAuth{HMACAuth: *hmacAuth.DeepCopy()} currentHMACAuth, err := sc.currentState.HMACAuths.Get(*hmacAuth.ID) if err == state.ErrNotFound { // hmacAuth not present, create it return &Event{ Op: crud.Create, Kind: "hmac-auth", Obj: hmacAuth, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up hmac-auth %v", *hmacAuth.Username) } // found, check if update needed if !currentHMACAuth.EqualWithOpts(hmacAuth, false, true, false) { return &Event{ Op: crud.Update, Kind: "hmac-auth", Obj: hmacAuth, OldObj: currentHMACAuth, }, nil } return nil, nil } deck-1.4.0/diff/jwtAuth.go000066400000000000000000000037221400603563700153170ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteJWTAuths() error { currentJWTAuths, err := sc.currentState.JWTAuths.GetAll() if err != nil { return errors.Wrap(err, "error fetching jwt-auths from state") } for _, jwtAuth := range currentJWTAuths { n, err := sc.deleteJWTAuth(jwtAuth) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteJWTAuth(jwtAuth *state.JWTAuth) (*Event, error) { _, err := sc.targetState.JWTAuths.Get(*jwtAuth.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "jwt-auth", Obj: jwtAuth, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up jwt-auth '%v'", *jwtAuth.Key) } return nil, nil } func (sc *Syncer) createUpdateJWTAuths() error { targetJWTAuths, err := sc.targetState.JWTAuths.GetAll() if err != nil { return errors.Wrap(err, "error fetching jwt-auths from state") } for _, jwtAuth := range targetJWTAuths { n, err := sc.createUpdateJWTAuth(jwtAuth) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateJWTAuth(jwtAuth *state.JWTAuth) (*Event, error) { jwtAuth = &state.JWTAuth{JWTAuth: *jwtAuth.DeepCopy()} currentJWTAuth, err := sc.currentState.JWTAuths.Get(*jwtAuth.ID) if err == state.ErrNotFound { // jwtAuth not present, create it return &Event{ Op: crud.Create, Kind: "jwt-auth", Obj: jwtAuth, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up jwt-auth %v", *jwtAuth.Key) } // found, check if update needed if !currentJWTAuth.EqualWithOpts(jwtAuth, false, true, false) { return &Event{ Op: crud.Update, Kind: "jwt-auth", Obj: jwtAuth, OldObj: currentJWTAuth, }, nil } return nil, nil } deck-1.4.0/diff/keyAuth.go000066400000000000000000000037201400603563700153010ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteKeyAuths() error { currentKeyAuths, err := sc.currentState.KeyAuths.GetAll() if err != nil { return errors.Wrap(err, "error fetching key-auths from state") } for _, keyAuth := range currentKeyAuths { n, err := sc.deleteKeyAuth(keyAuth) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteKeyAuth(keyAuth *state.KeyAuth) (*Event, error) { _, err := sc.targetState.KeyAuths.Get(*keyAuth.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "key-auth", Obj: keyAuth, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up key-auth '%v'", *keyAuth.ID) } return nil, nil } func (sc *Syncer) createUpdateKeyAuths() error { targetKeyAuths, err := sc.targetState.KeyAuths.GetAll() if err != nil { return errors.Wrap(err, "error fetching key-auths from state") } for _, keyAuth := range targetKeyAuths { n, err := sc.createUpdateKeyAuth(keyAuth) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateKeyAuth(keyAuth *state.KeyAuth) (*Event, error) { keyAuth = &state.KeyAuth{KeyAuth: *keyAuth.DeepCopy()} currentKeyAuth, err := sc.currentState.KeyAuths.Get(*keyAuth.ID) if err == state.ErrNotFound { // keyAuth not present, create it return &Event{ Op: crud.Create, Kind: "key-auth", Obj: keyAuth, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up key-auth %v", *keyAuth.ID) } // found, check if update needed if !currentKeyAuth.EqualWithOpts(keyAuth, false, true, false) { return &Event{ Op: crud.Update, Kind: "key-auth", Obj: keyAuth, OldObj: currentKeyAuth, }, nil } return nil, nil } deck-1.4.0/diff/mtlsAuth.go000066400000000000000000000037751400603563700155020ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteMTLSAuths() error { currentMTLSAuths, err := sc.currentState.MTLSAuths.GetAll() if err != nil { return errors.Wrap(err, "error fetching mtls-auths from state") } for _, mtlsAuth := range currentMTLSAuths { n, err := sc.deleteMTLSAuth(mtlsAuth) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteMTLSAuth(mtlsAuth *state.MTLSAuth) (*Event, error) { _, err := sc.targetState.MTLSAuths.Get(*mtlsAuth.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "mtls-auth", Obj: mtlsAuth, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up mtls-auth '%v'", *mtlsAuth.ID) } return nil, nil } func (sc *Syncer) createUpdateMTLSAuths() error { targetMTLSAuths, err := sc.targetState.MTLSAuths.GetAll() if err != nil { return errors.Wrap(err, "error fetching mtls-auths from state") } for _, mtlsAuth := range targetMTLSAuths { n, err := sc.createUpdateMTLSAuth(mtlsAuth) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateMTLSAuth(mtlsAuth *state.MTLSAuth) (*Event, error) { mtlsAuth = &state.MTLSAuth{MTLSAuth: *mtlsAuth.DeepCopy()} currentMTLSAuth, err := sc.currentState.MTLSAuths.Get(*mtlsAuth.ID) if err == state.ErrNotFound { // mtlsAuth not present, create it return &Event{ Op: crud.Create, Kind: "mtls-auth", Obj: mtlsAuth, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up mtls-auth %v", *mtlsAuth.ID) } // found, check if update needed if !currentMTLSAuth.EqualWithOpts(mtlsAuth, false, true, false) { return &Event{ Op: crud.Update, Kind: "mtls-auth", Obj: mtlsAuth, OldObj: currentMTLSAuth, }, nil } return nil, nil } deck-1.4.0/diff/oauth2.go000066400000000000000000000043221400603563700150700ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteOauth2Creds() error { currentOauth2Creds, err := sc.currentState.Oauth2Creds.GetAll() if err != nil { return errors.Wrap(err, "error fetching oauth2-cred from state") } for _, oauth2Cred := range currentOauth2Creds { n, err := sc.deleteOauth2Cred(oauth2Cred) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteOauth2Cred(oauth2Cred *state.Oauth2Credential) ( *Event, error) { _, err := sc.targetState.Oauth2Creds.Get(*oauth2Cred.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "oauth2-cred", Obj: oauth2Cred, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up oauth2-cred '%v'", *oauth2Cred.Name) } return nil, nil } func (sc *Syncer) createUpdateOauth2Creds() error { targetOauth2Creds, err := sc.targetState.Oauth2Creds.GetAll() if err != nil { return errors.Wrap(err, "error fetching oauth2-creds from state") } for _, oauth2Cred := range targetOauth2Creds { n, err := sc.createUpdateOauth2Cred(oauth2Cred) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateOauth2Cred(oauth2Cred *state.Oauth2Credential) (*Event, error) { oauth2Cred = &state.Oauth2Credential{Oauth2Credential: *oauth2Cred.DeepCopy()} currentOauth2Cred, err := sc.currentState.Oauth2Creds.Get(*oauth2Cred.ID) if err == state.ErrNotFound { // oauth2Cred not present, create it return &Event{ Op: crud.Create, Kind: "oauth2-cred", Obj: oauth2Cred, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up oauth2-cred %v", *oauth2Cred.Name) } currentOauth2Cred = &state.Oauth2Credential{Oauth2Credential: *currentOauth2Cred.DeepCopy()} // found, check if update needed if !currentOauth2Cred.EqualWithOpts(oauth2Cred, false, true, false) { return &Event{ Op: crud.Update, Kind: "oauth2-cred", Obj: oauth2Cred, OldObj: currentOauth2Cred, }, nil } return nil, nil } deck-1.4.0/diff/plugin.go000066400000000000000000000051111400603563700151610ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deletePlugins() error { currentPlugins, err := sc.currentState.Plugins.GetAll() if err != nil { return errors.Wrap(err, "error fetching plugins from state") } for _, plugin := range currentPlugins { n, err := sc.deletePlugin(plugin) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deletePlugin(plugin *state.Plugin) (*Event, error) { plugin = &state.Plugin{Plugin: *plugin.DeepCopy()} name := *plugin.Name serviceID, routeID, consumerID := foreignNames(plugin) _, err := sc.targetState.Plugins.GetByProp(name, serviceID, routeID, consumerID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "plugin", Obj: plugin, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up plugin '%v'", *plugin.ID) } return nil, nil } func (sc *Syncer) createUpdatePlugins() error { targetPlugins, err := sc.targetState.Plugins.GetAll() if err != nil { return errors.Wrap(err, "error fetching plugins from state") } for _, plugin := range targetPlugins { n, err := sc.createUpdatePlugin(plugin) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdatePlugin(plugin *state.Plugin) (*Event, error) { plugin = &state.Plugin{Plugin: *plugin.DeepCopy()} name := *plugin.Name serviceID, routeID, consumerID := foreignNames(plugin) currentPlugin, err := sc.currentState.Plugins.GetByProp(name, serviceID, routeID, consumerID) if err == state.ErrNotFound { // plugin not present, create it return &Event{ Op: crud.Create, Kind: "plugin", Obj: plugin, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up plugin %v", *plugin.Name) } currentPlugin = &state.Plugin{Plugin: *currentPlugin.DeepCopy()} // found, check if update needed if !currentPlugin.EqualWithOpts(plugin, false, true, false) { return &Event{ Op: crud.Update, Kind: "plugin", Obj: plugin, OldObj: currentPlugin, }, nil } return nil, nil } func foreignNames(p *state.Plugin) (serviceID, routeID, consumerID string) { if p == nil { return } if p.Service != nil && p.Service.ID != nil { serviceID = *p.Service.ID } if p.Route != nil && p.Route.ID != nil { routeID = *p.Route.ID } if p.Consumer != nil && p.Consumer.ID != nil { consumerID = *p.Consumer.ID } return } deck-1.4.0/diff/postProcess.go000066400000000000000000000210121400603563700162050ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" ) type servicePostAction struct { currentState *state.KongState } func (crud *servicePostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Services.Add(*args[0].(*state.Service)) } func (crud *servicePostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Services.Delete(*((args[0].(*state.Service)).ID)) } func (crud *servicePostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Services.Update(*args[0].(*state.Service)) } type routePostAction struct { currentState *state.KongState } func (crud *routePostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Routes.Add(*args[0].(*state.Route)) } func (crud *routePostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Routes.Delete(*((args[0].(*state.Route)).ID)) } func (crud *routePostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Routes.Update(*args[0].(*state.Route)) } type upstreamPostAction struct { currentState *state.KongState } func (crud *upstreamPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Upstreams.Add(*args[0].(*state.Upstream)) } func (crud *upstreamPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Upstreams.Delete(*((args[0].(*state.Upstream)).ID)) } func (crud *upstreamPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Upstreams.Update(*args[0].(*state.Upstream)) } type targetPostAction struct { currentState *state.KongState } func (crud *targetPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Targets.Add(*args[0].(*state.Target)) } func (crud *targetPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { target := args[0].(*state.Target) return nil, crud.currentState.Targets.Delete(*target.Upstream.ID, *target.ID) } func (crud *targetPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Targets.Update(*args[0].(*state.Target)) } type certificatePostAction struct { currentState *state.KongState } func (crud *certificatePostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Certificates.Add(*args[0].(*state.Certificate)) } func (crud *certificatePostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Certificates.Delete(*((args[0].(*state.Certificate)).ID)) } func (crud *certificatePostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Certificates.Update(*args[0].(*state.Certificate)) } type sniPostAction struct { currentState *state.KongState } func (crud *sniPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.SNIs.Add(*args[0].(*state.SNI)) } func (crud *sniPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { sni := args[0].(*state.SNI) return nil, crud.currentState.SNIs.Delete(*sni.ID) } func (crud *sniPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.SNIs.Update(*args[0].(*state.SNI)) } type caCertificatePostAction struct { currentState *state.KongState } func (crud *caCertificatePostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.CACertificates.Add(*args[0].(*state.CACertificate)) } func (crud *caCertificatePostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.CACertificates.Delete(*((args[0].(*state.CACertificate)).ID)) } func (crud *caCertificatePostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.CACertificates.Update(*args[0].(*state.CACertificate)) } type pluginPostAction struct { currentState *state.KongState } func (crud *pluginPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Plugins.Add(*args[0].(*state.Plugin)) } func (crud *pluginPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Plugins.Delete(*((args[0].(*state.Plugin)).ID)) } func (crud *pluginPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Plugins.Update(*args[0].(*state.Plugin)) } type consumerPostAction struct { currentState *state.KongState } func (crud *consumerPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Consumers.Add(*args[0].(*state.Consumer)) } func (crud *consumerPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Consumers.Delete(*((args[0].(*state.Consumer)).ID)) } func (crud *consumerPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Consumers.Update(*args[0].(*state.Consumer)) } type keyAuthPostAction struct { currentState *state.KongState } func (crud *keyAuthPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.KeyAuths.Add(*args[0].(*state.KeyAuth)) } func (crud *keyAuthPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.KeyAuths.Delete(*((args[0].(*state.KeyAuth)).ID)) } func (crud *keyAuthPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.KeyAuths.Update(*args[0].(*state.KeyAuth)) } type hmacAuthPostAction struct { currentState *state.KongState } func (crud hmacAuthPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.HMACAuths.Add(*args[0].(*state.HMACAuth)) } func (crud hmacAuthPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.HMACAuths.Delete(*((args[0].(*state.HMACAuth)).ID)) } func (crud hmacAuthPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.HMACAuths.Update(*args[0].(*state.HMACAuth)) } type jwtAuthPostAction struct { currentState *state.KongState } func (crud jwtAuthPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.JWTAuths.Add(*args[0].(*state.JWTAuth)) } func (crud jwtAuthPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.JWTAuths.Delete(*((args[0].(*state.JWTAuth)).ID)) } func (crud jwtAuthPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.JWTAuths.Update(*args[0].(*state.JWTAuth)) } type basicAuthPostAction struct { currentState *state.KongState } func (crud basicAuthPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.BasicAuths.Add(*args[0].(*state.BasicAuth)) } func (crud basicAuthPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.BasicAuths.Delete(*((args[0].(*state.BasicAuth)).ID)) } func (crud basicAuthPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.BasicAuths.Update(*args[0].(*state.BasicAuth)) } type aclGroupPostAction struct { currentState *state.KongState } func (crud aclGroupPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.ACLGroups.Add(*args[0].(*state.ACLGroup)) } func (crud aclGroupPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.ACLGroups.Delete(*((args[0].(*state.ACLGroup)).ID)) } func (crud aclGroupPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.ACLGroups.Update(*args[0].(*state.ACLGroup)) } type oauth2CredPostAction struct { currentState *state.KongState } func (crud oauth2CredPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Oauth2Creds.Add(*args[0].(*state.Oauth2Credential)) } func (crud oauth2CredPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Oauth2Creds.Delete(*((args[0].(*state.Oauth2Credential)).ID)) } func (crud oauth2CredPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.Oauth2Creds.Update(*args[0].(*state.Oauth2Credential)) } type mtlsAuthPostAction struct { currentState *state.KongState } func (crud *mtlsAuthPostAction) Create(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.MTLSAuths.Add(*args[0].(*state.MTLSAuth)) } func (crud *mtlsAuthPostAction) Delete(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.MTLSAuths.Delete(*((args[0].(*state.MTLSAuth)).ID)) } func (crud *mtlsAuthPostAction) Update(args ...crud.Arg) (crud.Arg, error) { return nil, crud.currentState.MTLSAuths.Update(*args[0].(*state.MTLSAuth)) } deck-1.4.0/diff/route.go000066400000000000000000000036031400603563700150250ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteRoutes() error { currentRoutes, err := sc.currentState.Routes.GetAll() if err != nil { return errors.Wrap(err, "error fetching routes from state") } for _, route := range currentRoutes { n, err := sc.deleteRoute(route) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteRoute(route *state.Route) (*Event, error) { _, err := sc.targetState.Routes.Get(*route.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "route", Obj: route, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up route '%v'", route.Identifier()) } return nil, nil } func (sc *Syncer) createUpdateRoutes() error { targetRoutes, err := sc.targetState.Routes.GetAll() if err != nil { return errors.Wrap(err, "error fetching routes from state") } for _, route := range targetRoutes { n, err := sc.createUpdateRoute(route) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateRoute(route *state.Route) (*Event, error) { route = &state.Route{Route: *route.DeepCopy()} currentRoute, err := sc.currentState.Routes.Get(*route.ID) if err == state.ErrNotFound { // route not present, create it return &Event{ Op: crud.Create, Kind: "route", Obj: route, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up route %v", route.Identifier()) } // found, check if update needed if !currentRoute.EqualWithOpts(route, false, true, false) { return &Event{ Op: crud.Update, Kind: "route", Obj: route, OldObj: currentRoute, }, nil } return nil, nil } deck-1.4.0/diff/service.go000066400000000000000000000036751400603563700153400ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteServices() error { currentServices, err := sc.currentState.Services.GetAll() if err != nil { return errors.Wrap(err, "error fetching services from state") } for _, service := range currentServices { n, err := sc.deleteService(service) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteService(service *state.Service) (*Event, error) { _, err := sc.targetState.Services.Get(*service.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "service", Obj: service, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up service '%v'", service.Identifier()) } return nil, nil } func (sc *Syncer) createUpdateServices() error { targetServices, err := sc.targetState.Services.GetAll() if err != nil { return errors.Wrap(err, "error fetching services from state") } for _, service := range targetServices { n, err := sc.createUpdateService(service) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateService(service *state.Service) (*Event, error) { serviceCopy := &state.Service{Service: *service.DeepCopy()} currentService, err := sc.currentState.Services.Get(*service.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Create, Kind: "service", Obj: serviceCopy, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up service %v", *service.Name) } // found, check if update needed if !currentService.EqualWithOpts(serviceCopy, false, true) { return &Event{ Op: crud.Update, Kind: "service", Obj: serviceCopy, OldObj: currentService, }, nil } return nil, nil } deck-1.4.0/diff/sni.go000066400000000000000000000034171400603563700144630ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteSNIs() error { currentSNIs, err := sc.currentState.SNIs.GetAll() if err != nil { return errors.Wrap(err, "error fetching snis from state") } for _, sni := range currentSNIs { n, err := sc.deleteSNI(sni) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteSNI(sni *state.SNI) (*Event, error) { _, err := sc.targetState.SNIs.Get(*sni.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "sni", Obj: sni, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up sni '%v'", *sni.Name) } return nil, nil } func (sc *Syncer) createUpdateSNIs() error { sniSNIs, err := sc.targetState.SNIs.GetAll() if err != nil { return errors.Wrap(err, "error fetching snis from state") } for _, sni := range sniSNIs { n, err := sc.createUpdateSNI(sni) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateSNI(sni *state.SNI) (*Event, error) { sni = &state.SNI{SNI: *sni.DeepCopy()} currentSNI, err := sc.currentState.SNIs.Get(*sni.ID) if err == state.ErrNotFound { // sni not present, create it return &Event{ Op: crud.Create, Kind: "sni", Obj: sni, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up sni %v", *sni.Name) } // found, check if update needed if !currentSNI.EqualWithOpts(sni, false, true, false) { return &Event{ Op: crud.Update, Kind: "sni", Obj: sni, OldObj: currentSNI, }, nil } return nil, nil } deck-1.4.0/diff/target.go000066400000000000000000000037601400603563700151610ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteTargets() error { currentTargets, err := sc.currentState.Targets.GetAll() if err != nil { return errors.Wrap(err, "error fetching targets from state") } for _, target := range currentTargets { n, err := sc.deleteTarget(target) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteTarget(target *state.Target) (*Event, error) { _, err := sc.targetState.Targets.Get(*target.Upstream.ID, *target.Target.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "target", Obj: target, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up target '%v'", *target.Target.Target) } return nil, nil } func (sc *Syncer) createUpdateTargets() error { targetTargets, err := sc.targetState.Targets.GetAll() if err != nil { return errors.Wrap(err, "error fetching targets from state") } for _, target := range targetTargets { n, err := sc.createUpdateTarget(target) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateTarget(target *state.Target) (*Event, error) { target = &state.Target{Target: *target.DeepCopy()} currentTarget, err := sc.currentState.Targets.Get(*target.Upstream.ID, *target.Target.ID) if err == state.ErrNotFound { // target not present, create it return &Event{ Op: crud.Create, Kind: "target", Obj: target, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up target %v", *target.Target.Target) } // found, check if update needed if !currentTarget.EqualWithOpts(target, false, true, false) { return &Event{ Op: crud.Update, Kind: "target", Obj: target, OldObj: currentTarget, }, nil } return nil, nil } deck-1.4.0/diff/types.go000066400000000000000000000004101400603563700150240ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" ) // Event represents an event to perform // an imperative operation // that gets Kong closer to the target state. type Event struct { Op crud.Op Kind crud.Kind Obj interface{} OldObj interface{} } deck-1.4.0/diff/upstream.go000066400000000000000000000037451400603563700155360ustar00rootroot00000000000000package diff import ( "github.com/kong/deck/crud" "github.com/kong/deck/state" "github.com/pkg/errors" ) func (sc *Syncer) deleteUpstreams() error { currentUpstreams, err := sc.currentState.Upstreams.GetAll() if err != nil { return errors.Wrap(err, "error fetching upstreams from state") } for _, upstream := range currentUpstreams { n, err := sc.deleteUpstream(upstream) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) deleteUpstream(upstream *state.Upstream) (*Event, error) { _, err := sc.targetState.Upstreams.Get(*upstream.ID) if err == state.ErrNotFound { return &Event{ Op: crud.Delete, Kind: "upstream", Obj: upstream, }, nil } if err != nil { return nil, errors.Wrapf(err, "looking up upstream '%v'", *upstream.Name) } return nil, nil } func (sc *Syncer) createUpdateUpstreams() error { targetUpstreams, err := sc.targetState.Upstreams.GetAll() if err != nil { return errors.Wrap(err, "error fetching upstreams from state") } for _, upstream := range targetUpstreams { n, err := sc.createUpdateUpstream(upstream) if err != nil { return err } if n != nil { err = sc.queueEvent(*n) if err != nil { return err } } } return nil } func (sc *Syncer) createUpdateUpstream(upstream *state.Upstream) (*Event, error) { upstreamCopy := &state.Upstream{Upstream: *upstream.DeepCopy()} currentUpstream, err := sc.currentState.Upstreams.Get(*upstream.Name) if err == state.ErrNotFound { return &Event{ Op: crud.Create, Kind: "upstream", Obj: upstreamCopy, }, nil } if err != nil { return nil, errors.Wrapf(err, "error looking up upstream %v", *upstream.Name) } // found, check if update needed if !currentUpstream.EqualWithOpts(upstreamCopy, false, true) { return &Event{ Op: crud.Update, Kind: "upstream", Obj: upstreamCopy, OldObj: currentUpstream, }, nil } return nil, nil } deck-1.4.0/docs/000077500000000000000000000000001400603563700133565ustar00rootroot00000000000000deck-1.4.0/docs/CNAME000066400000000000000000000000171400603563700141220ustar00rootroot00000000000000deck.yolo42.comdeck-1.4.0/docs/README.md000066400000000000000000000001751400603563700146400ustar00rootroot00000000000000# decK Documentation This site has moved to [https://docs.konghq.com/deck/overview](https://docs.konghq.com/deck/overview). deck-1.4.0/docs/_config.yml000066400000000000000000000002131400603563700155010ustar00rootroot00000000000000theme: jekyll-theme-slate google_analytics: UA-39705803-13 title: decK description: Declarative configuration for Kong and Kong Enterprise deck-1.4.0/docs/development/000077500000000000000000000000001400603563700157005ustar00rootroot00000000000000deck-1.4.0/docs/development/release.md000066400000000000000000000040621400603563700176440ustar00rootroot00000000000000# Releasing a version ## Prepare - Ensure your local Go version is up to date. It should match the Go version in `Dockerfile`. - Create a `docs/changelog-v0.5.0` branch. Substitute v0.5.0 with the version you are releasing. - Write the changelog. Ensure the links are all created correctly and make sure to write the doc with end-user in mind. - Commit and push the branch to Github. Ensure that the Markdown is rendered correctly. Make changes as needed. The link on the version itself won't resolve correctly as the tag is not yet created. - Ensure you have Goreleaser and Docker installed locally. - Once the changelog looks good, open a PR to `main` and wait for review. ## Release - After the changelog is merged, `git checkout main; git pull`. - Tag the `HEAD` with your version, e.g. `git tag v0.5.0` - Push the tag to remote (Github), e.g. `git push --tags` - Run Goreleaser: `goreleaser release --rm-dist`. This will create a release in Github and upload all the artifacts. - Edit the release to remove all the commit messages as the content and instead add a link to the changelog. Refer to older releases for reference. - Clone https://github.com/Kong/homebrew-deck. Copy deck.rb from your deck repo folder to homebrew-deck, e.g. `cp /path/to/deck/dist/deck.rb /path/to/homebrew-deck/Formula/deck.rb`. `git diff` in homebrew-deck to confirm that only the version and checksum have changed. Commit changes with a "release v0.5.0" message and push master to origin. ## Docker release Assuming you are on the TAG commit, you need to perform the following: ``` export TAG=$(git describe --abbrev=0 --tags) export COMMIT=$(git rev-parse --short $TAG) docker build --build-arg TAG=$TAG --build-arg COMMIT=$COMMIT -t hbagdi/deck:$TAG . docker push hbagdi/deck:$TAG docker tag hbagdi/deck:$TAG kong/deck:$TAG docker push kong/deck:$TAG # if also the latest release (not for a back-ported patch release): docker tag hbagdi/deck:$TAG hbagdi/deck:latest docker push hbagdi/deck:latest docker tag hbagdi/deck:latest kong/deck:latest docker push kong/deck:latest ``` deck-1.4.0/dump/000077500000000000000000000000001400603563700133735ustar00rootroot00000000000000deck-1.4.0/dump/dump.go000066400000000000000000000347721400603563700147040ustar00rootroot00000000000000package dump import ( "context" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) // Config can be used to skip exporting certain entities type Config struct { // If true, consumers and any plugins associated with it // are not exported. SkipConsumers bool // SelectorTags can be used to export entities tagged with only specific // tags. SelectorTags []string } func deduplicate(stringSlice []string) []string { existing := map[string]struct{}{} result := []string{} for _, s := range stringSlice { if _, exists := existing[s]; !exists { existing[s] = struct{}{} result = append(result, s) } } return result } func newOpt(tags []string) *kong.ListOpt { opt := new(kong.ListOpt) opt.Size = 1000 opt.Tags = kong.StringSlice(deduplicate(tags)...) opt.MatchAllTags = true return opt } // Get queries all the entities using client and returns // all the entities in KongRawState. func Get(client *kong.Client, config Config) (*utils.KongRawState, error) { var state utils.KongRawState group, ctx := errgroup.WithContext(context.Background()) group.Go(func() error { services, err := GetAllServices(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "services") } state.Services = services return nil }) group.Go(func() error { routes, err := GetAllRoutes(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "routes") } state.Routes = routes return nil }) group.Go(func() error { plugins, err := GetAllPlugins(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "plugins") } if config.SkipConsumers { state.Plugins = excludeConsumersPlugins(plugins) } else { state.Plugins = plugins } return nil }) group.Go(func() error { certificates, err := GetAllCertificates(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "certificates") } state.Certificates = certificates return nil }) group.Go(func() error { caCerts, err := GetAllCACertificates(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "ca-certificates") } state.CACertificates = caCerts return nil }) group.Go(func() error { snis, err := GetAllSNIs(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "snis") } state.SNIs = snis return nil }) group.Go(func() error { upstreams, err := GetAllUpstreams(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "upstreams") } state.Upstreams = upstreams targets, err := GetAllTargets(ctx, client, upstreams, config.SelectorTags) if err != nil { return errors.Wrap(err, "targets") } state.Targets = targets return nil }) if !config.SkipConsumers { group.Go(func() error { consumers, err := GetAllConsumers(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "consumers") } state.Consumers = consumers return nil }) group.Go(func() error { keyAuths, err := GetAllKeyAuths(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "key-auths") } state.KeyAuths = keyAuths return nil }) group.Go(func() error { hmacAuths, err := GetAllHMACAuths(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "hmac-auths") } state.HMACAuths = hmacAuths return nil }) group.Go(func() error { jwtAuths, err := GetAllJWTAuths(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "jwts") } state.JWTAuths = jwtAuths return nil }) group.Go(func() error { basicAuths, err := GetAllBasicAuths(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "basic-auths") } state.BasicAuths = basicAuths return nil }) group.Go(func() error { oauth2Creds, err := GetAllOauth2Creds(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "oauth2") } state.Oauth2Creds = oauth2Creds return nil }) group.Go(func() error { aclGroups, err := GetAllACLGroups(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "acls") } state.ACLGroups = aclGroups return nil }) group.Go(func() error { mtlsAuths, err := GetAllMTLSAuths(ctx, client, config.SelectorTags) if err != nil { return errors.Wrap(err, "mtls-auths") } state.MTLSAuths = mtlsAuths return nil }) } err := group.Wait() if err != nil { return nil, err } return &state, nil } // GetAllServices queries Kong for all the services using client. func GetAllServices(ctx context.Context, client *kong.Client, tags []string) ([]*kong.Service, error) { var services []*kong.Service opt := newOpt(tags) for { s, nextopt, err := client.Services.List(ctx, opt) if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } services = append(services, s...) if nextopt == nil { break } opt = nextopt } return services, nil } // GetAllRoutes queries Kong for all the routes using client. func GetAllRoutes(ctx context.Context, client *kong.Client, tags []string) ([]*kong.Route, error) { var routes []*kong.Route opt := newOpt(tags) for { s, nextopt, err := client.Routes.List(ctx, opt) if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } routes = append(routes, s...) if nextopt == nil { break } opt = nextopt } return routes, nil } // GetAllPlugins queries Kong for all the plugins using client. func GetAllPlugins(ctx context.Context, client *kong.Client, tags []string) ([]*kong.Plugin, error) { var plugins []*kong.Plugin opt := newOpt(tags) for { s, nextopt, err := client.Plugins.List(ctx, opt) if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } plugins = append(plugins, s...) if nextopt == nil { break } opt = nextopt } return plugins, nil } // GetAllCertificates queries Kong for all the certificates using client. func GetAllCertificates(ctx context.Context, client *kong.Client, tags []string) ([]*kong.Certificate, error) { var certificates []*kong.Certificate opt := newOpt(tags) for { s, nextopt, err := client.Certificates.List(ctx, opt) if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } for _, cert := range s { c := cert c.SNIs = nil certificates = append(certificates, cert) } if nextopt == nil { break } opt = nextopt } return certificates, nil } // GetAllCACertificates queries Kong for all the CACertificates using client. func GetAllCACertificates(ctx context.Context, client *kong.Client, tags []string) ([]*kong.CACertificate, error) { var caCertificates []*kong.CACertificate opt := newOpt(tags) for { s, nextopt, err := client.CACertificates.List(nil, opt) // Compatibility for Kong < 1.3 // This core entitiy was not present in the past // and the Admin API request will error with 404 Not Found // If we do get the error, we return back an empty array of // CACertificates, effectively disabling the entity for versions // which don't have it. // A better solution would be to have a version check, and based // on the version, the entities are loaded and synced. if err != nil { if kong.IsNotFoundErr(err) { return caCertificates, nil } return nil, err } if err := ctx.Err(); err != nil { return nil, err } caCertificates = append(caCertificates, s...) if nextopt == nil { break } opt = nextopt } return caCertificates, nil } // GetAllSNIs queries Kong for all the SNIs using client. func GetAllSNIs(ctx context.Context, client *kong.Client, tags []string) ([]*kong.SNI, error) { var snis []*kong.SNI opt := newOpt(tags) for { s, nextopt, err := client.SNIs.List(ctx, opt) if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } snis = append(snis, s...) if nextopt == nil { break } opt = nextopt } return snis, nil } // GetAllConsumers queries Kong for all the consumers using client. // Please use this method with caution if you have a lot of consumers. func GetAllConsumers(ctx context.Context, client *kong.Client, tags []string) ([]*kong.Consumer, error) { var consumers []*kong.Consumer opt := newOpt(tags) for { s, nextopt, err := client.Consumers.List(ctx, opt) if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } consumers = append(consumers, s...) if nextopt == nil { break } opt = nextopt } return consumers, nil } // GetAllUpstreams queries Kong for all the Upstreams using client. func GetAllUpstreams(ctx context.Context, client *kong.Client, tags []string) ([]*kong.Upstream, error) { var upstreams []*kong.Upstream opt := newOpt(tags) for { s, nextopt, err := client.Upstreams.List(ctx, opt) if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } upstreams = append(upstreams, s...) if nextopt == nil { break } opt = nextopt } return upstreams, nil } // GetAllTargets queries Kong for all the Targets of upstreams using client. // Targets are queries per upstream as there exists no endpoint in Kong // to list all targets of all upstreams. func GetAllTargets(ctx context.Context, client *kong.Client, upstreams []*kong.Upstream, tags []string) ([]*kong.Target, error) { var targets []*kong.Target opt := newOpt(tags) for _, upstream := range upstreams { for { t, nextopt, err := client.Targets.List(ctx, upstream.ID, opt) if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } targets = append(targets, t...) if nextopt == nil { break } opt = nextopt } } return targets, nil } // GetAllKeyAuths queries Kong for all key-auth credentials using client. func GetAllKeyAuths(ctx context.Context, client *kong.Client, tags []string) ([]*kong.KeyAuth, error) { var keyAuths []*kong.KeyAuth // tags are not supported on credentials // opt := newOpt(tags) opt := newOpt(nil) for { s, nextopt, err := client.KeyAuths.List(ctx, opt) if kong.IsNotFoundErr(err) { return keyAuths, nil } if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } keyAuths = append(keyAuths, s...) if nextopt == nil { break } opt = nextopt } return keyAuths, nil } // GetAllHMACAuths queries Kong for all hmac-auth credentials using client. func GetAllHMACAuths(ctx context.Context, client *kong.Client, tags []string) ([]*kong.HMACAuth, error) { var hmacAuths []*kong.HMACAuth // tags are not supported on credentials // opt := newOpt(tags) opt := newOpt(nil) for { s, nextopt, err := client.HMACAuths.List(ctx, opt) if kong.IsNotFoundErr(err) { return hmacAuths, nil } if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } hmacAuths = append(hmacAuths, s...) if nextopt == nil { break } opt = nextopt } return hmacAuths, nil } // GetAllJWTAuths queries Kong for all jwt credentials using client. func GetAllJWTAuths(ctx context.Context, client *kong.Client, tags []string) ([]*kong.JWTAuth, error) { var jwtAuths []*kong.JWTAuth // tags are not supported on credentials // opt := newOpt(tags) opt := newOpt(nil) for { s, nextopt, err := client.JWTAuths.List(ctx, opt) if kong.IsNotFoundErr(err) { return jwtAuths, nil } if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } jwtAuths = append(jwtAuths, s...) if nextopt == nil { break } opt = nextopt } return jwtAuths, nil } // GetAllBasicAuths queries Kong for all basic-auth credentials using client. func GetAllBasicAuths(ctx context.Context, client *kong.Client, tags []string) ([]*kong.BasicAuth, error) { var basicAuths []*kong.BasicAuth // tags are not supported on credentials // opt := newOpt(tags) opt := newOpt(nil) for { s, nextopt, err := client.BasicAuths.List(ctx, opt) if kong.IsNotFoundErr(err) { return basicAuths, nil } if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } basicAuths = append(basicAuths, s...) if nextopt == nil { break } opt = nextopt } return basicAuths, nil } // GetAllOauth2Creds queries Kong for all oauth2 credentials using client. func GetAllOauth2Creds(ctx context.Context, client *kong.Client, tags []string) ([]*kong.Oauth2Credential, error) { var oauth2Creds []*kong.Oauth2Credential // tags are not supported on credentials // opt := newOpt(tags) opt := newOpt(nil) for { s, nextopt, err := client.Oauth2Credentials.List(ctx, opt) if kong.IsNotFoundErr(err) { return oauth2Creds, nil } if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } oauth2Creds = append(oauth2Creds, s...) if nextopt == nil { break } opt = nextopt } return oauth2Creds, nil } // GetAllACLGroups queries Kong for all ACL groups using client. func GetAllACLGroups(ctx context.Context, client *kong.Client, tags []string) ([]*kong.ACLGroup, error) { var aclGroups []*kong.ACLGroup // tags are not supported on credentials // opt := newOpt(tags) opt := newOpt(nil) for { s, nextopt, err := client.ACLs.List(ctx, opt) if kong.IsNotFoundErr(err) { return aclGroups, nil } if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } aclGroups = append(aclGroups, s...) if nextopt == nil { break } opt = nextopt } return aclGroups, nil } // GetAllMTLSAuths queries Kong for all basic-auth credentials using client. func GetAllMTLSAuths(ctx context.Context, client *kong.Client, tags []string) ([]*kong.MTLSAuth, error) { var mtlsAuths []*kong.MTLSAuth // tags are not supported on credentials // opt := newOpt(tags) opt := newOpt(nil) for { s, nextopt, err := client.MTLSAuths.List(ctx, opt) if kong.IsNotFoundErr(err) { return mtlsAuths, nil } if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } mtlsAuths = append(mtlsAuths, s...) if nextopt == nil { break } opt = nextopt } return mtlsAuths, nil } // excludeConsumersPlugins filter out consumer plugins func excludeConsumersPlugins(plugins []*kong.Plugin) []*kong.Plugin { var filtered []*kong.Plugin for _, p := range plugins { if p.Consumer != nil && !utils.Empty(p.Consumer.ID) { continue } filtered = append(filtered, p) } return filtered } deck-1.4.0/examples/000077500000000000000000000000001400603563700142445ustar00rootroot00000000000000deck-1.4.0/examples/deck.yml000066400000000000000000000004031400603563700156720ustar00rootroot00000000000000# sample configuration file for global parameters of deck CLI. kong-addr: http://localhost:8001 headers: - "key1:value1" no-color: false verbose: 0 # tls-skip-verify: false # tls-server-name: my-server-name.example.com # ca_cert : custom PEM encoded CA cert deck-1.4.0/file/000077500000000000000000000000001400603563700133455ustar00rootroot00000000000000deck-1.4.0/file/builder.go000066400000000000000000000374711400603563700153360ustar00rootroot00000000000000package file import ( "github.com/blang/semver" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" "github.com/pkg/errors" ) type stateBuilder struct { targetContent *Content rawState *utils.KongRawState currentState *state.KongState defaulter *utils.Defaulter kongVersion semver.Version selectTags []string intermediate *state.KongState certIDs map[string]bool err error } var ( kong140Version = semver.MustParse("1.4.0") ) // uuid generates a UUID string and returns a pointer to it. // It is a variable for testing purpose, to override and supply // a deterministic UUID generator. var uuid = func() *string { return kong.String(utils.UUID()) } func (b *stateBuilder) build() (*utils.KongRawState, error) { // setup var err error b.rawState = &utils.KongRawState{} if b.targetContent.Info != nil { b.selectTags = b.targetContent.Info.SelectorTags } b.intermediate, err = state.NewKongState() if err != nil { return nil, err } b.certIDs = map[string]bool{} // build b.certificates() b.caCertificates() b.services() b.routes() b.upstreams() b.consumers() b.plugins() // result if b.err != nil { return nil, b.err } return b.rawState, nil } func (b *stateBuilder) certificates() { if b.err != nil { return } for i := range b.targetContent.Certificates { c := b.targetContent.Certificates[i] if utils.Empty(c.ID) { cert, err := b.currentState.Certificates.GetByCertKey(*c.Cert, *c.Key) if err == state.ErrNotFound { c.ID = uuid() } else if err != nil { b.err = err return } else { c.ID = kong.String(*cert.ID) } } utils.MustMergeTags(&c, b.selectTags) snisFromCert := c.SNIs kongCert := kong.Certificate{ ID: c.ID, Key: c.Key, Cert: c.Cert, Tags: c.Tags, CreatedAt: c.CreatedAt, } b.rawState.Certificates = append(b.rawState.Certificates, &kongCert) // snis associated with the certificate var snis []kong.SNI for _, sni := range snisFromCert { sni.Certificate = &kong.Certificate{ID: kong.String(*c.ID)} snis = append(snis, sni) } if err := b.ingestSNIs(snis); err != nil { b.err = err return } b.certIDs[*c.ID] = true } } func (b *stateBuilder) ingestSNIs(snis []kong.SNI) error { for _, sni := range snis { sni := sni if utils.Empty(sni.ID) { currentSNI, err := b.currentState.SNIs.Get(*sni.Name) if err == state.ErrNotFound { sni.ID = uuid() } else if err != nil { return err } else { sni.ID = kong.String(*currentSNI.ID) } } utils.MustMergeTags(&sni, b.selectTags) b.rawState.SNIs = append(b.rawState.SNIs, &sni) } return nil } func (b *stateBuilder) caCertificates() { if b.err != nil { return } for _, c := range b.targetContent.CACertificates { c := c if utils.Empty(c.ID) { cert, err := b.currentState.CACertificates.Get(*c.Cert) if err == state.ErrNotFound { c.ID = uuid() } else if err != nil { b.err = err return } else { c.ID = kong.String(*cert.ID) } } utils.MustMergeTags(&c.CACertificate, b.selectTags) b.rawState.CACertificates = append(b.rawState.CACertificates, &c.CACertificate) } } func (b *stateBuilder) consumers() { if b.err != nil { return } for _, c := range b.targetContent.Consumers { c := c if utils.Empty(c.ID) { consumer, err := b.currentState.Consumers.Get(*c.Username) if err == state.ErrNotFound { c.ID = uuid() } else if err != nil { b.err = err return } else { c.ID = kong.String(*consumer.ID) } } utils.MustMergeTags(&c.Consumer, b.selectTags) b.rawState.Consumers = append(b.rawState.Consumers, &c.Consumer) err := b.intermediate.Consumers.Add(state.Consumer{Consumer: c.Consumer}) if err != nil { b.err = err return } // plugins for the Consumer var plugins []FPlugin for _, p := range c.Plugins { p.Consumer = &kong.Consumer{ID: kong.String(*c.ID)} plugins = append(plugins, *p) } if err := b.ingestPlugins(plugins); err != nil { b.err = err return } var keyAuths []kong.KeyAuth for _, cred := range c.KeyAuths { cred.Consumer = &kong.Consumer{ID: kong.String(*c.ID)} keyAuths = append(keyAuths, *cred) } if err := b.ingestKeyAuths(keyAuths); err != nil { b.err = err return } var basicAuths []kong.BasicAuth for _, cred := range c.BasicAuths { cred.Consumer = &kong.Consumer{ID: kong.String(*c.ID)} basicAuths = append(basicAuths, *cred) } if err := b.ingestBasicAuths(basicAuths); err != nil { b.err = err return } var hmacAuths []kong.HMACAuth for _, cred := range c.HMACAuths { cred.Consumer = &kong.Consumer{ID: kong.String(*c.ID)} hmacAuths = append(hmacAuths, *cred) } if err := b.ingestHMACAuths(hmacAuths); err != nil { b.err = err return } var jwtAuths []kong.JWTAuth for _, cred := range c.JWTAuths { cred.Consumer = &kong.Consumer{ID: kong.String(*c.ID)} jwtAuths = append(jwtAuths, *cred) } if err := b.ingestJWTAuths(jwtAuths); err != nil { b.err = err return } var oauth2Creds []kong.Oauth2Credential for _, cred := range c.Oauth2Creds { cred.Consumer = &kong.Consumer{ID: kong.String(*c.ID)} oauth2Creds = append(oauth2Creds, *cred) } if err := b.ingestOauth2Creds(oauth2Creds); err != nil { b.err = err return } var aclGroups []kong.ACLGroup for _, cred := range c.ACLGroups { cred.Consumer = &kong.Consumer{ID: kong.String(*c.ID)} aclGroups = append(aclGroups, *cred) } if err := b.ingestACLGroups(aclGroups); err != nil { b.err = err return } var mtlsAuths []kong.MTLSAuth for _, cred := range c.MTLSAuths { cred.Consumer = &kong.Consumer{ ID: kong.String(*c.ID), } mtlsAuths = append(mtlsAuths, *cred) } b.ingestMTLSAuths(mtlsAuths) } } func (b *stateBuilder) ingestKeyAuths(creds []kong.KeyAuth) error { for _, cred := range creds { cred := cred if utils.Empty(cred.ID) { existingCred, err := b.currentState.KeyAuths.Get(*cred.Key) if err == state.ErrNotFound { cred.ID = uuid() } else if err != nil { return err } else { cred.ID = kong.String(*existingCred.ID) } } if b.kongVersion.GTE(kong140Version) { utils.MustMergeTags(&cred, b.selectTags) } b.rawState.KeyAuths = append(b.rawState.KeyAuths, &cred) } return nil } func (b *stateBuilder) ingestBasicAuths(creds []kong.BasicAuth) error { for _, cred := range creds { cred := cred if utils.Empty(cred.ID) { existingCred, err := b.currentState.BasicAuths.Get(*cred.Username) if err == state.ErrNotFound { cred.ID = uuid() } else if err != nil { return err } else { cred.ID = kong.String(*existingCred.ID) } } if b.kongVersion.GTE(kong140Version) { utils.MustMergeTags(&cred, b.selectTags) } b.rawState.BasicAuths = append(b.rawState.BasicAuths, &cred) } return nil } func (b *stateBuilder) ingestHMACAuths(creds []kong.HMACAuth) error { for _, cred := range creds { cred := cred if utils.Empty(cred.ID) { existingCred, err := b.currentState.HMACAuths.Get(*cred.Username) if err == state.ErrNotFound { cred.ID = uuid() } else if err != nil { return err } else { cred.ID = kong.String(*existingCred.ID) } } if b.kongVersion.GTE(kong140Version) { utils.MustMergeTags(&cred, b.selectTags) } b.rawState.HMACAuths = append(b.rawState.HMACAuths, &cred) } return nil } func (b *stateBuilder) ingestJWTAuths(creds []kong.JWTAuth) error { for _, cred := range creds { cred := cred if utils.Empty(cred.ID) { existingCred, err := b.currentState.JWTAuths.Get(*cred.Key) if err == state.ErrNotFound { cred.ID = uuid() } else if err != nil { return err } else { cred.ID = kong.String(*existingCred.ID) } } if b.kongVersion.GTE(kong140Version) { utils.MustMergeTags(&cred, b.selectTags) } b.rawState.JWTAuths = append(b.rawState.JWTAuths, &cred) } return nil } func (b *stateBuilder) ingestOauth2Creds(creds []kong.Oauth2Credential) error { for _, cred := range creds { cred := cred if utils.Empty(cred.ID) { existingCred, err := b.currentState.Oauth2Creds.Get(*cred.ClientID) if err == state.ErrNotFound { cred.ID = uuid() } else if err != nil { return err } else { cred.ID = kong.String(*existingCred.ID) } } if b.kongVersion.GTE(kong140Version) { utils.MustMergeTags(&cred, b.selectTags) } b.rawState.Oauth2Creds = append(b.rawState.Oauth2Creds, &cred) } return nil } func (b *stateBuilder) ingestACLGroups(creds []kong.ACLGroup) error { for _, cred := range creds { cred := cred if utils.Empty(cred.ID) { existingCred, err := b.currentState.ACLGroups.Get( *cred.Consumer.ID, *cred.Group) if err == state.ErrNotFound { cred.ID = uuid() } else if err != nil { return err } else { cred.ID = kong.String(*existingCred.ID) } } if b.kongVersion.GTE(kong140Version) { utils.MustMergeTags(&cred, b.selectTags) } b.rawState.ACLGroups = append(b.rawState.ACLGroups, &cred) } return nil } func (b *stateBuilder) ingestMTLSAuths(creds []kong.MTLSAuth) { for _, cred := range creds { cred := cred // normally, we'd want to look up existing resources in this case // however, this is impossible here: mtls-auth simply has no unique fields other than ID, // so we don't--schema validation requires the ID // there's nothing more to do here // TODO: this is stub code, since mtls-auth doesn't actually have tag support yet // They probably should, FTI-1706 tracks that request with the Kong Enterprise team //if b.kongVersion.GTE(kong220Version) { // utils.MustMergeTags(&cred, b.selectTags) //} b.rawState.MTLSAuths = append(b.rawState.MTLSAuths, &cred) } } func (b *stateBuilder) services() { if b.err != nil { return } for _, s := range b.targetContent.Services { s := s if utils.Empty(s.ID) { svc, err := b.currentState.Services.Get(*s.Name) if err == state.ErrNotFound { s.ID = uuid() } else if err != nil { b.err = err return } else { s.ID = kong.String(*svc.ID) } } utils.MustMergeTags(&s.Service, b.selectTags) b.defaulter.MustSet(&s.Service) if s.ClientCertificate != nil && !utils.Empty(s.ClientCertificate.ID) { if _, ok := b.certIDs[*s.ClientCertificate.ID]; !ok { b.err = errors.Errorf("client certificate not found: %v", *s.ClientCertificate.ID) return } } b.rawState.Services = append(b.rawState.Services, &s.Service) err := b.intermediate.Services.Add(state.Service{Service: s.Service}) if err != nil { b.err = err return } // plugins for the service var plugins []FPlugin for _, p := range s.Plugins { p.Service = &kong.Service{ID: kong.String(*s.ID)} plugins = append(plugins, *p) } if err := b.ingestPlugins(plugins); err != nil { b.err = err return } // routes for the service for _, r := range s.Routes { r := r r.Service = &kong.Service{ID: kong.String(*s.ID)} if err := b.ingestRoute(*r); err != nil { b.err = err return } } } } func (b *stateBuilder) routes() { if b.err != nil { return } for _, r := range b.targetContent.Routes { r := r if err := b.ingestRoute(r); err != nil { b.err = err return } } } func (b *stateBuilder) upstreams() { if b.err != nil { return } for _, u := range b.targetContent.Upstreams { u := u if utils.Empty(u.ID) { ups, err := b.currentState.Upstreams.Get(*u.Name) if err == state.ErrNotFound { u.ID = uuid() } else if err != nil { b.err = err return } else { u.ID = kong.String(*ups.ID) } } utils.MustMergeTags(&u.Upstream, b.selectTags) b.defaulter.MustSet(&u.Upstream) b.rawState.Upstreams = append(b.rawState.Upstreams, &u.Upstream) // targets for the upstream var targets []kong.Target for _, t := range u.Targets { t.Upstream = &kong.Upstream{ID: kong.String(*u.ID)} targets = append(targets, t.Target) } if err := b.ingestTargets(targets); err != nil { b.err = err return } } } func (b *stateBuilder) ingestTargets(targets []kong.Target) error { for _, t := range targets { t := t if utils.Empty(t.ID) { target, err := b.currentState.Targets.Get(*t.Upstream.ID, *t.Target) if err == state.ErrNotFound { t.ID = uuid() } else if err != nil { return err } else { t.ID = kong.String(*target.ID) } } utils.MustMergeTags(&t, b.selectTags) b.defaulter.MustSet(&t) b.rawState.Targets = append(b.rawState.Targets, &t) } return nil } func (b *stateBuilder) plugins() { if b.err != nil { return } var plugins []FPlugin 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) if err == state.ErrNotFound { b.err = errors.Wrapf(err, "consumer %v for plugin %v", *p.Consumer.ID, *p.Name) return } else if err != nil { b.err = err return } p.Consumer = &kong.Consumer{ID: kong.String(*c.ID)} } if p.Service != nil && !utils.Empty(p.Service.ID) { s, err := b.intermediate.Services.Get(*p.Service.ID) if err == state.ErrNotFound { b.err = errors.Wrapf(err, "service %v for plugin %v", *p.Service.ID, *p.Name) return } else if err != nil { b.err = err return } p.Service = &kong.Service{ID: kong.String(*s.ID)} } if p.Route != nil && !utils.Empty(p.Route.ID) { s, err := b.intermediate.Routes.Get(*p.Route.ID) if err == state.ErrNotFound { b.err = errors.Wrapf(err, "route %v for plugin %v", *p.Route.ID, *p.Name) return } else if err != nil { b.err = err return } p.Route = &kong.Route{ID: kong.String(*s.ID)} } plugins = append(plugins, p) } if err := b.ingestPlugins(plugins); err != nil { b.err = err return } } func (b *stateBuilder) ingestRoute(r FRoute) error { if utils.Empty(r.ID) { route, err := b.currentState.Routes.Get(*r.Name) if err == state.ErrNotFound { r.ID = uuid() } else if err != nil { return err } else { r.ID = kong.String(*route.ID) } } utils.MustMergeTags(&r, b.selectTags) b.defaulter.MustSet(&r.Route) b.rawState.Routes = append(b.rawState.Routes, &r.Route) err := b.intermediate.Routes.Add(state.Route{Route: r.Route}) if err != nil { return err } // plugins for the route var plugins []FPlugin for _, p := range r.Plugins { p.Route = &kong.Route{ID: kong.String(*r.ID)} plugins = append(plugins, *p) } if err := b.ingestPlugins(plugins); err != nil { return err } return nil } func (b *stateBuilder) ingestPlugins(plugins []FPlugin) error { for _, p := range plugins { p := p if utils.Empty(p.ID) { cID, rID, sID := pluginRelations(&p.Plugin) plugin, err := b.currentState.Plugins.GetByProp(*p.Name, sID, rID, cID) if err == state.ErrNotFound { p.ID = uuid() } else if err != nil { return err } else { p.ID = kong.String(*plugin.ID) } } if p.Config == nil { p.Config = make(map[string]interface{}) } p.Config = ensureJSON(p.Config) err := b.fillPluginConfig(&p) if err != nil { return err } utils.MustMergeTags(&p, b.selectTags) b.rawState.Plugins = append(b.rawState.Plugins, &p.Plugin) } return nil } func (b *stateBuilder) fillPluginConfig(plugin *FPlugin) error { if plugin == nil { return errors.New("plugin is nil") } if !utils.Empty(plugin.ConfigSource) { conf, ok := b.targetContent.PluginConfigs[*plugin.ConfigSource] if !ok { return errors.Errorf("_plugin_config '%v' not found", *plugin.ConfigSource) } for k, v := range conf { if _, ok := plugin.Config[k]; !ok { plugin.Config[k] = v } } } return nil } func pluginRelations(plugin *kong.Plugin) (cID, rID, sID string) { if plugin.Consumer != nil && !utils.Empty(plugin.Consumer.ID) { cID = *plugin.Consumer.ID } if plugin.Route != nil && !utils.Empty(plugin.Route.ID) { rID = *plugin.Route.ID } if plugin.Service != nil && !utils.Empty(plugin.Service.ID) { sID = *plugin.Service.ID } return } deck-1.4.0/file/builder_test.go000066400000000000000000001476331400603563700163770ustar00rootroot00000000000000package file import ( "encoding/hex" "math/rand" "os" "reflect" "testing" "github.com/blang/semver" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) var ( kong130Version = semver.MustParse("1.3.0") ) func emptyState() *state.KongState { s, _ := state.NewKongState() return s } func existingRouteState() *state.KongState { s, _ := state.NewKongState() s.Routes.Add(state.Route{ Route: kong.Route{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Name: kong.String("foo"), }, }) return s } func existingServiceState() *state.KongState { s, _ := state.NewKongState() s.Services.Add(state.Service{ Service: kong.Service{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Name: kong.String("foo"), }, }) return s } func existingConsumerCredState() *state.KongState { s, _ := state.NewKongState() s.Consumers.Add(state.Consumer{ Consumer: kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Username: kong.String("foo"), }, }) s.KeyAuths.Add(state.KeyAuth{ KeyAuth: kong.KeyAuth{ ID: kong.String("5f1ef1ea-a2a5-4a1b-adbb-b0d3434013e5"), Key: kong.String("foo-apikey"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }) s.BasicAuths.Add(state.BasicAuth{ BasicAuth: kong.BasicAuth{ ID: kong.String("92f4c849-960b-43af-aad3-f307051408d3"), Username: kong.String("basic-username"), Password: kong.String("basic-password"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }) s.JWTAuths.Add(state.JWTAuth{ JWTAuth: kong.JWTAuth{ ID: kong.String("917b9402-1be0-49d2-b482-ca4dccc2054e"), Key: kong.String("jwt-key"), Secret: kong.String("jwt-secret"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }) s.HMACAuths.Add(state.HMACAuth{ HMACAuth: kong.HMACAuth{ ID: kong.String("e5d81b73-bf9e-42b0-9d68-30a1d791b9c9"), Username: kong.String("hmac-username"), Secret: kong.String("hmac-secret"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }) s.ACLGroups.Add(state.ACLGroup{ ACLGroup: kong.ACLGroup{ ID: kong.String("b7c9352a-775a-4ba5-9869-98e926a3e6cb"), Group: kong.String("foo-group"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }) s.Oauth2Creds.Add(state.Oauth2Credential{ Oauth2Credential: kong.Oauth2Credential{ ID: kong.String("4eef5285-3d6a-4f6b-b659-8957a940e2ca"), ClientID: kong.String("oauth2-clientid"), Name: kong.String("oauth2-name"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }) s.MTLSAuths.Add(state.MTLSAuth{ MTLSAuth: kong.MTLSAuth{ ID: kong.String("92f4c829-968b-42af-afd3-f337051508d3"), SubjectName: kong.String("test@example.com"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }) return s } func existingUpstreamState() *state.KongState { s, _ := state.NewKongState() s.Upstreams.Add(state.Upstream{ Upstream: kong.Upstream{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Name: kong.String("foo"), }, }) return s } func existingCertificateState() *state.KongState { s, _ := state.NewKongState() s.Certificates.Add(state.Certificate{ Certificate: kong.Certificate{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Cert: kong.String("foo"), Key: kong.String("bar"), }, }) return s } func existingCertificateAndSNIState() *state.KongState { s, _ := state.NewKongState() s.Certificates.Add(state.Certificate{ Certificate: kong.Certificate{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Cert: kong.String("foo"), Key: kong.String("bar"), }, }) s.SNIs.Add(state.SNI{ SNI: kong.SNI{ ID: kong.String("a53e9598-3a5e-4c12-a672-71a4cdcf7a47"), Name: kong.String("foo.example.com"), Certificate: &kong.Certificate{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }) s.SNIs.Add(state.SNI{ SNI: kong.SNI{ ID: kong.String("5f8e6848-4cb9-479a-a27e-860e1a77f875"), Name: kong.String("bar.example.com"), Certificate: &kong.Certificate{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }) return s } func existingCACertificateState() *state.KongState { s, _ := state.NewKongState() s.CACertificates.Add(state.CACertificate{ CACertificate: kong.CACertificate{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Cert: kong.String("foo"), }, }) return s } func existingPluginState() *state.KongState { s, _ := state.NewKongState() s.Plugins.Add(state.Plugin{ Plugin: kong.Plugin{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Name: kong.String("foo"), }, }) s.Plugins.Add(state.Plugin{ Plugin: kong.Plugin{ ID: kong.String("f7e64af5-e438-4a9b-8ff8-ec6f5f06dccb"), Name: kong.String("bar"), Consumer: &kong.Consumer{ ID: kong.String("f77ca8c7-581d-45a4-a42c-c003234228e1"), }, }, }) s.Plugins.Add(state.Plugin{ Plugin: kong.Plugin{ ID: kong.String("53ce0a9c-d518-40ee-b8ab-1ee83a20d382"), Name: kong.String("foo"), Consumer: &kong.Consumer{ ID: kong.String("f77ca8c7-581d-45a4-a42c-c003234228e1"), }, Route: &kong.Route{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, }, }) return s } func existingTargetsState() *state.KongState { s, _ := state.NewKongState() s.Targets.Add(state.Target{ Target: kong.Target{ ID: kong.String("f7e64af5-e438-4a9b-8ff8-ec6f5f06dccb"), Target: kong.String("bar"), Upstream: &kong.Upstream{ ID: kong.String("f77ca8c7-581d-45a4-a42c-c003234228e1"), }, }, }) s.Targets.Add(state.Target{ Target: kong.Target{ ID: kong.String("53ce0a9c-d518-40ee-b8ab-1ee83a20d382"), Target: kong.String("foo"), Upstream: &kong.Upstream{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, }, }) return s } var deterministicUUID = func() *string { version := byte(4) uuid := make([]byte, 16) rand.Read(uuid) // Set version uuid[6] = (uuid[6] & 0x0f) | (version << 4) // Set variant uuid[8] = (uuid[8] & 0xbf) | 0x80 buf := make([]byte, 36) var dash byte = '-' hex.Encode(buf[0:8], uuid[0:4]) buf[8] = dash hex.Encode(buf[9:13], uuid[4:6]) buf[13] = dash hex.Encode(buf[14:18], uuid[6:8]) buf[18] = dash hex.Encode(buf[19:23], uuid[8:10]) buf[23] = dash hex.Encode(buf[24:], uuid[10:]) s := string(buf) return &s } func TestMain(m *testing.M) { uuid = deterministicUUID os.Exit(m.Run()) } func Test_stateBuilder_services(t *testing.T) { assert := assert.New(t) rand.Seed(42) type fields struct { targetContent *Content currentState *state.KongState } tests := []struct { name string fields fields want *utils.KongRawState }{ { name: "matches ID of an existing service", fields: fields{ targetContent: &Content{ Info: &Info{ SelectorTags: []string{"tag1"}, }, Services: []FService{ { Service: kong.Service{ Name: kong.String("foo"), }, }, }, }, currentState: existingServiceState(), }, want: &utils.KongRawState{ Services: []*kong.Service{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Name: kong.String("foo"), Port: kong.Int(80), Protocol: kong.String("http"), ConnectTimeout: kong.Int(60000), WriteTimeout: kong.Int(60000), ReadTimeout: kong.Int(60000), Tags: kong.StringSlice("tag1"), }, }, }, }, { name: "process a non-existent service", fields: fields{ targetContent: &Content{ Services: []FService{ { Service: kong.Service{ Name: kong.String("foo"), }, }, }, }, currentState: emptyState(), }, want: &utils.KongRawState{ Services: []*kong.Service{ { ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), Name: kong.String("foo"), Port: kong.Int(80), Protocol: kong.String("http"), ConnectTimeout: kong.Int(60000), WriteTimeout: kong.Int(60000), ReadTimeout: kong.Int(60000), }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &stateBuilder{ targetContent: tt.fields.targetContent, currentState: tt.fields.currentState, } d, _ := utils.GetKongDefaulter() b.defaulter = d b.build() assert.Equal(tt.want, b.rawState) }) } } func Test_stateBuilder_ingestRoute(t *testing.T) { assert := assert.New(t) rand.Seed(42) type fields struct { currentState *state.KongState } type args struct { route FRoute } tests := []struct { name string fields fields args args wantErr bool wantState *utils.KongRawState }{ { name: "generates ID for a non-existing route", fields: fields{ currentState: emptyState(), }, args: args{ route: FRoute{ Route: kong.Route{ Name: kong.String("foo"), }, }, }, wantErr: false, wantState: &utils.KongRawState{ Routes: []*kong.Route{ { ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), Name: kong.String("foo"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), }, }, }, }, { name: "matches up IDs of routes correctly", fields: fields{ currentState: existingRouteState(), }, args: args{ route: FRoute{ Route: kong.Route{ Name: kong.String("foo"), }, }, }, wantErr: false, wantState: &utils.KongRawState{ Routes: []*kong.Route{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Name: kong.String("foo"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &stateBuilder{ currentState: tt.fields.currentState, } b.rawState = &utils.KongRawState{} d, _ := utils.GetKongDefaulter() b.defaulter = d b.intermediate, _ = state.NewKongState() if err := b.ingestRoute(tt.args.route); (err != nil) != tt.wantErr { t.Errorf("stateBuilder.ingestPlugins() error = %v, wantErr %v", err, tt.wantErr) } assert.Equal(tt.wantState, b.rawState) }) } } func Test_stateBuilder_ingestTargets(t *testing.T) { assert := assert.New(t) rand.Seed(42) type fields struct { currentState *state.KongState } type args struct { targets []kong.Target } tests := []struct { name string fields fields args args wantErr bool wantState *utils.KongRawState }{ { name: "generates ID for a non-existing target", fields: fields{ currentState: emptyState(), }, args: args{ targets: []kong.Target{ { Target: kong.String("foo"), Upstream: &kong.Upstream{ ID: kong.String("952ddf37-e815-40b6-b119-5379a3b1f7be"), }, }, }, }, wantErr: false, wantState: &utils.KongRawState{ Targets: []*kong.Target{ { ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), Target: kong.String("foo"), Weight: kong.Int(100), Upstream: &kong.Upstream{ ID: kong.String("952ddf37-e815-40b6-b119-5379a3b1f7be"), }, }, }, }, }, { name: "matches up IDs of Targets correctly", fields: fields{ currentState: existingTargetsState(), }, args: args{ targets: []kong.Target{ { Target: kong.String("bar"), Upstream: &kong.Upstream{ ID: kong.String("f77ca8c7-581d-45a4-a42c-c003234228e1"), }, }, { Target: kong.String("foo"), Upstream: &kong.Upstream{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, }, }, }, wantErr: false, wantState: &utils.KongRawState{ Targets: []*kong.Target{ { ID: kong.String("f7e64af5-e438-4a9b-8ff8-ec6f5f06dccb"), Target: kong.String("bar"), Weight: kong.Int(100), Upstream: &kong.Upstream{ ID: kong.String("f77ca8c7-581d-45a4-a42c-c003234228e1"), }, }, { ID: kong.String("53ce0a9c-d518-40ee-b8ab-1ee83a20d382"), Target: kong.String("foo"), Weight: kong.Int(100), Upstream: &kong.Upstream{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &stateBuilder{ currentState: tt.fields.currentState, } b.rawState = &utils.KongRawState{} d, _ := utils.GetKongDefaulter() b.defaulter = d if err := b.ingestTargets(tt.args.targets); (err != nil) != tt.wantErr { t.Errorf("stateBuilder.ingestPlugins() error = %v, wantErr %v", err, tt.wantErr) } assert.Equal(tt.wantState, b.rawState) }) } } func Test_stateBuilder_ingestPlugins(t *testing.T) { assert := assert.New(t) rand.Seed(42) type fields struct { currentState *state.KongState } type args struct { plugins []FPlugin } tests := []struct { name string fields fields args args wantErr bool wantState *utils.KongRawState }{ { name: "generates ID for a non-existing plugin", fields: fields{ currentState: emptyState(), }, args: args{ plugins: []FPlugin{ { Plugin: kong.Plugin{ Name: kong.String("foo"), }, }, }, }, wantErr: false, wantState: &utils.KongRawState{ Plugins: []*kong.Plugin{ { ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), Name: kong.String("foo"), Config: kong.Configuration{}, }, }, }, }, { name: "matches up IDs of plugins correctly", fields: fields{ currentState: existingPluginState(), }, args: args{ plugins: []FPlugin{ { Plugin: kong.Plugin{ Name: kong.String("foo"), }, }, { Plugin: kong.Plugin{ Name: kong.String("bar"), Consumer: &kong.Consumer{ ID: kong.String("f77ca8c7-581d-45a4-a42c-c003234228e1"), }, }, }, { Plugin: kong.Plugin{ Name: kong.String("foo"), Consumer: &kong.Consumer{ ID: kong.String("f77ca8c7-581d-45a4-a42c-c003234228e1"), }, Route: &kong.Route{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, }, }, }, }, wantErr: false, wantState: &utils.KongRawState{ Plugins: []*kong.Plugin{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Name: kong.String("foo"), Config: kong.Configuration{}, }, { ID: kong.String("f7e64af5-e438-4a9b-8ff8-ec6f5f06dccb"), Name: kong.String("bar"), Consumer: &kong.Consumer{ ID: kong.String("f77ca8c7-581d-45a4-a42c-c003234228e1"), }, Config: kong.Configuration{}, }, { ID: kong.String("53ce0a9c-d518-40ee-b8ab-1ee83a20d382"), Name: kong.String("foo"), Consumer: &kong.Consumer{ ID: kong.String("f77ca8c7-581d-45a4-a42c-c003234228e1"), }, Route: &kong.Route{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, Config: kong.Configuration{}, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &stateBuilder{ currentState: tt.fields.currentState, } b.rawState = &utils.KongRawState{} if err := b.ingestPlugins(tt.args.plugins); (err != nil) != tt.wantErr { t.Errorf("stateBuilder.ingestPlugins() error = %v, wantErr %v", err, tt.wantErr) } assert.Equal(tt.wantState, b.rawState) }) } } func Test_pluginRelations(t *testing.T) { type args struct { plugin *kong.Plugin } tests := []struct { name string args args wantCID string wantRID string wantSID string }{ { args: args{ plugin: &kong.Plugin{ Name: kong.String("foo"), }, }, wantCID: "", wantRID: "", wantSID: "", }, { args: args{ plugin: &kong.Plugin{ Name: kong.String("foo"), Consumer: &kong.Consumer{ ID: kong.String("cID"), }, Route: &kong.Route{ ID: kong.String("rID"), }, Service: &kong.Service{ ID: kong.String("sID"), }, }, }, wantCID: "cID", wantRID: "rID", wantSID: "sID", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotCID, gotRID, gotSID := pluginRelations(tt.args.plugin) if gotCID != tt.wantCID { t.Errorf("pluginRelations() gotCID = %v, want %v", gotCID, tt.wantCID) } if gotRID != tt.wantRID { t.Errorf("pluginRelations() gotRID = %v, want %v", gotRID, tt.wantRID) } if gotSID != tt.wantSID { t.Errorf("pluginRelations() gotSID = %v, want %v", gotSID, tt.wantSID) } }) } } func Test_stateBuilder_consumers(t *testing.T) { assert := assert.New(t) rand.Seed(42) type fields struct { currentState *state.KongState targetContent *Content kongVersion *semver.Version } tests := []struct { name string fields fields want *utils.KongRawState }{ { name: "generates ID for a non-existing consumer", fields: fields{ targetContent: &Content{ Consumers: []FConsumer{ { Consumer: kong.Consumer{ Username: kong.String("foo"), }, }, }, Info: &Info{ SelectorTags: []string{"tag1"}, }, }, currentState: emptyState(), }, want: &utils.KongRawState{ Consumers: []*kong.Consumer{ { ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), Username: kong.String("foo"), Tags: kong.StringSlice("tag1"), }, }, }, }, { name: "generates ID for a non-existing credential", fields: fields{ targetContent: &Content{ Consumers: []FConsumer{ { Consumer: kong.Consumer{ Username: kong.String("foo"), }, KeyAuths: []*kong.KeyAuth{ { Key: kong.String("foo-key"), }, }, BasicAuths: []*kong.BasicAuth{ { Username: kong.String("basic-username"), Password: kong.String("basic-password"), }, }, HMACAuths: []*kong.HMACAuth{ { Username: kong.String("hmac-username"), Secret: kong.String("hmac-secret"), }, }, JWTAuths: []*kong.JWTAuth{ { Key: kong.String("jwt-key"), Secret: kong.String("jwt-secret"), }, }, Oauth2Creds: []*kong.Oauth2Credential{ { ClientID: kong.String("oauth2-clientid"), Name: kong.String("oauth2-name"), }, }, ACLGroups: []*kong.ACLGroup{ { Group: kong.String("foo-group"), }, }, }, }, Info: &Info{ SelectorTags: []string{"tag1"}, }, }, currentState: emptyState(), }, want: &utils.KongRawState{ Consumers: []*kong.Consumer{ { ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), Username: kong.String("foo"), Tags: kong.StringSlice("tag1"), }, }, KeyAuths: []*kong.KeyAuth{ { ID: kong.String("dfd79b4d-7642-4b61-ba0c-9f9f0d3ba55b"), Key: kong.String("foo-key"), Consumer: &kong.Consumer{ ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), }, Tags: kong.StringSlice("tag1"), }, }, BasicAuths: []*kong.BasicAuth{ { ID: kong.String("0cc0d614-4c88-4535-841a-cbe0709b0758"), Username: kong.String("basic-username"), Password: kong.String("basic-password"), Consumer: &kong.Consumer{ ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), }, Tags: kong.StringSlice("tag1"), }, }, HMACAuths: []*kong.HMACAuth{ { ID: kong.String("083f61d3-75bc-42b4-9df4-f91929e18fda"), Username: kong.String("hmac-username"), Secret: kong.String("hmac-secret"), Consumer: &kong.Consumer{ ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), }, Tags: kong.StringSlice("tag1"), }, }, JWTAuths: []*kong.JWTAuth{ { ID: kong.String("9e6f82e5-4e74-4e81-a79e-4bbd6fe34cdc"), Key: kong.String("jwt-key"), Secret: kong.String("jwt-secret"), Consumer: &kong.Consumer{ ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), }, Tags: kong.StringSlice("tag1"), }, }, Oauth2Creds: []*kong.Oauth2Credential{ { ID: kong.String("ba843ee8-d63e-4c4f-be1c-ebea546d8fac"), ClientID: kong.String("oauth2-clientid"), Name: kong.String("oauth2-name"), Consumer: &kong.Consumer{ ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), }, Tags: kong.StringSlice("tag1"), }, }, ACLGroups: []*kong.ACLGroup{ { ID: kong.String("13dd1aac-04ce-4ea2-877c-5579cfa2c78e"), Group: kong.String("foo-group"), Consumer: &kong.Consumer{ ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), }, Tags: kong.StringSlice("tag1"), }, }, MTLSAuths: nil, }, }, { name: "matches ID of an existing consumer", fields: fields{ targetContent: &Content{ Consumers: []FConsumer{ { Consumer: kong.Consumer{ Username: kong.String("foo"), }, }, }, }, currentState: existingConsumerCredState(), }, want: &utils.KongRawState{ Consumers: []*kong.Consumer{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Username: kong.String("foo"), }, }, }, }, { name: "matches ID of an existing credential", fields: fields{ targetContent: &Content{ Consumers: []FConsumer{ { Consumer: kong.Consumer{ Username: kong.String("foo"), }, KeyAuths: []*kong.KeyAuth{ { Key: kong.String("foo-apikey"), }, }, BasicAuths: []*kong.BasicAuth{ { Username: kong.String("basic-username"), Password: kong.String("basic-password"), }, }, HMACAuths: []*kong.HMACAuth{ { Username: kong.String("hmac-username"), Secret: kong.String("hmac-secret"), }, }, JWTAuths: []*kong.JWTAuth{ { Key: kong.String("jwt-key"), Secret: kong.String("jwt-secret"), }, }, Oauth2Creds: []*kong.Oauth2Credential{ { ClientID: kong.String("oauth2-clientid"), Name: kong.String("oauth2-name"), }, }, ACLGroups: []*kong.ACLGroup{ { Group: kong.String("foo-group"), }, }, MTLSAuths: []*kong.MTLSAuth{ { ID: kong.String("533c259e-bf71-4d77-99d2-97944c70a6a4"), SubjectName: kong.String("test@example.com"), }, }, }, }, Info: &Info{ SelectorTags: []string{"tag1"}, }, }, currentState: existingConsumerCredState(), }, want: &utils.KongRawState{ Consumers: []*kong.Consumer{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Username: kong.String("foo"), Tags: kong.StringSlice("tag1"), }, }, KeyAuths: []*kong.KeyAuth{ { ID: kong.String("5f1ef1ea-a2a5-4a1b-adbb-b0d3434013e5"), Key: kong.String("foo-apikey"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, Tags: kong.StringSlice("tag1"), }, }, BasicAuths: []*kong.BasicAuth{ { ID: kong.String("92f4c849-960b-43af-aad3-f307051408d3"), Username: kong.String("basic-username"), Password: kong.String("basic-password"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, Tags: kong.StringSlice("tag1"), }, }, HMACAuths: []*kong.HMACAuth{ { ID: kong.String("e5d81b73-bf9e-42b0-9d68-30a1d791b9c9"), Username: kong.String("hmac-username"), Secret: kong.String("hmac-secret"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, Tags: kong.StringSlice("tag1"), }, }, JWTAuths: []*kong.JWTAuth{ { ID: kong.String("917b9402-1be0-49d2-b482-ca4dccc2054e"), Key: kong.String("jwt-key"), Secret: kong.String("jwt-secret"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, Tags: kong.StringSlice("tag1"), }, }, Oauth2Creds: []*kong.Oauth2Credential{ { ID: kong.String("4eef5285-3d6a-4f6b-b659-8957a940e2ca"), ClientID: kong.String("oauth2-clientid"), Name: kong.String("oauth2-name"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, Tags: kong.StringSlice("tag1"), }, }, ACLGroups: []*kong.ACLGroup{ { ID: kong.String("b7c9352a-775a-4ba5-9869-98e926a3e6cb"), Group: kong.String("foo-group"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, Tags: kong.StringSlice("tag1"), }, }, MTLSAuths: []*kong.MTLSAuth{ { ID: kong.String("533c259e-bf71-4d77-99d2-97944c70a6a4"), SubjectName: kong.String("test@example.com"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }, }, }, { name: "does not inject tags if Kong version is older than 1.4", fields: fields{ targetContent: &Content{ Consumers: []FConsumer{ { Consumer: kong.Consumer{ Username: kong.String("foo"), }, KeyAuths: []*kong.KeyAuth{ { Key: kong.String("foo-apikey"), }, }, BasicAuths: []*kong.BasicAuth{ { Username: kong.String("basic-username"), Password: kong.String("basic-password"), }, }, HMACAuths: []*kong.HMACAuth{ { Username: kong.String("hmac-username"), Secret: kong.String("hmac-secret"), }, }, JWTAuths: []*kong.JWTAuth{ { Key: kong.String("jwt-key"), Secret: kong.String("jwt-secret"), }, }, Oauth2Creds: []*kong.Oauth2Credential{ { ClientID: kong.String("oauth2-clientid"), Name: kong.String("oauth2-name"), }, }, ACLGroups: []*kong.ACLGroup{ { Group: kong.String("foo-group"), }, }, MTLSAuths: []*kong.MTLSAuth{ { ID: kong.String("533c259e-bf71-4d77-99d2-97944c70a6a4"), SubjectName: kong.String("test@example.com"), }, }, }, }, Info: &Info{ SelectorTags: []string{"tag1"}, }, }, currentState: existingConsumerCredState(), kongVersion: &kong130Version, }, want: &utils.KongRawState{ Consumers: []*kong.Consumer{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Username: kong.String("foo"), Tags: kong.StringSlice("tag1"), }, }, KeyAuths: []*kong.KeyAuth{ { ID: kong.String("5f1ef1ea-a2a5-4a1b-adbb-b0d3434013e5"), Key: kong.String("foo-apikey"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }, BasicAuths: []*kong.BasicAuth{ { ID: kong.String("92f4c849-960b-43af-aad3-f307051408d3"), Username: kong.String("basic-username"), Password: kong.String("basic-password"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }, HMACAuths: []*kong.HMACAuth{ { ID: kong.String("e5d81b73-bf9e-42b0-9d68-30a1d791b9c9"), Username: kong.String("hmac-username"), Secret: kong.String("hmac-secret"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }, JWTAuths: []*kong.JWTAuth{ { ID: kong.String("917b9402-1be0-49d2-b482-ca4dccc2054e"), Key: kong.String("jwt-key"), Secret: kong.String("jwt-secret"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }, Oauth2Creds: []*kong.Oauth2Credential{ { ID: kong.String("4eef5285-3d6a-4f6b-b659-8957a940e2ca"), ClientID: kong.String("oauth2-clientid"), Name: kong.String("oauth2-name"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }, ACLGroups: []*kong.ACLGroup{ { ID: kong.String("b7c9352a-775a-4ba5-9869-98e926a3e6cb"), Group: kong.String("foo-group"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }, MTLSAuths: []*kong.MTLSAuth{ { ID: kong.String("533c259e-bf71-4d77-99d2-97944c70a6a4"), SubjectName: kong.String("test@example.com"), Consumer: &kong.Consumer{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &stateBuilder{ targetContent: tt.fields.targetContent, currentState: tt.fields.currentState, kongVersion: kong140Version, } if tt.fields.kongVersion != nil { b.kongVersion = *tt.fields.kongVersion } d, _ := utils.GetKongDefaulter() b.defaulter = d b.build() assert.Equal(tt.want, b.rawState) }) } } func Test_stateBuilder_certificates(t *testing.T) { assert := assert.New(t) rand.Seed(42) type fields struct { currentState *state.KongState targetContent *Content } tests := []struct { name string fields fields want *utils.KongRawState }{ { name: "generates ID for a non-existing certificate", fields: fields{ targetContent: &Content{ Certificates: []FCertificate{ { Cert: kong.String("foo"), Key: kong.String("bar"), }, }, }, currentState: emptyState(), }, want: &utils.KongRawState{ Certificates: []*kong.Certificate{ { ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), Cert: kong.String("foo"), Key: kong.String("bar"), }, }, }, }, { name: "matches ID of an existing certificate", fields: fields{ targetContent: &Content{ Certificates: []FCertificate{ { Cert: kong.String("foo"), Key: kong.String("bar"), }, }, }, currentState: existingCertificateState(), }, want: &utils.KongRawState{ Certificates: []*kong.Certificate{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Cert: kong.String("foo"), Key: kong.String("bar"), }, }, }, }, { name: "generates ID for SNIs", fields: fields{ targetContent: &Content{ Certificates: []FCertificate{ { Cert: kong.String("foo"), Key: kong.String("bar"), SNIs: []kong.SNI{ { Name: kong.String("foo.example.com"), }, { Name: kong.String("bar.example.com"), }, }, }, }, }, currentState: existingCertificateState(), }, want: &utils.KongRawState{ Certificates: []*kong.Certificate{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Cert: kong.String("foo"), Key: kong.String("bar"), }, }, SNIs: []*kong.SNI{ { ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), Name: kong.String("foo.example.com"), Certificate: &kong.Certificate{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, { ID: kong.String("dfd79b4d-7642-4b61-ba0c-9f9f0d3ba55b"), Name: kong.String("bar.example.com"), Certificate: &kong.Certificate{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }, }, }, { name: "matches ID for SNIs", fields: fields{ targetContent: &Content{ Certificates: []FCertificate{ { Cert: kong.String("foo"), Key: kong.String("bar"), SNIs: []kong.SNI{ { Name: kong.String("foo.example.com"), }, { Name: kong.String("bar.example.com"), }, }, }, }, }, currentState: existingCertificateAndSNIState(), }, want: &utils.KongRawState{ Certificates: []*kong.Certificate{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Cert: kong.String("foo"), Key: kong.String("bar"), }, }, SNIs: []*kong.SNI{ { ID: kong.String("a53e9598-3a5e-4c12-a672-71a4cdcf7a47"), Name: kong.String("foo.example.com"), Certificate: &kong.Certificate{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, { ID: kong.String("5f8e6848-4cb9-479a-a27e-860e1a77f875"), Name: kong.String("bar.example.com"), Certificate: &kong.Certificate{ ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), }, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &stateBuilder{ targetContent: tt.fields.targetContent, currentState: tt.fields.currentState, } d, _ := utils.GetKongDefaulter() b.defaulter = d b.build() assert.Equal(tt.want, b.rawState) }) } } func Test_stateBuilder_caCertificates(t *testing.T) { assert := assert.New(t) rand.Seed(42) type fields struct { currentState *state.KongState targetContent *Content } tests := []struct { name string fields fields want *utils.KongRawState }{ { name: "generates ID for a non-existing CACertificate", fields: fields{ targetContent: &Content{ CACertificates: []FCACertificate{ { CACertificate: kong.CACertificate{ Cert: kong.String("foo"), }, }, }, }, currentState: emptyState(), }, want: &utils.KongRawState{ CACertificates: []*kong.CACertificate{ { ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), Cert: kong.String("foo"), }, }, }, }, { name: "matches ID of an existing CACertificate", fields: fields{ targetContent: &Content{ CACertificates: []FCACertificate{ { CACertificate: kong.CACertificate{ Cert: kong.String("foo"), }, }, }, }, currentState: existingCACertificateState(), }, want: &utils.KongRawState{ CACertificates: []*kong.CACertificate{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Cert: kong.String("foo"), }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &stateBuilder{ targetContent: tt.fields.targetContent, currentState: tt.fields.currentState, } d, _ := utils.GetKongDefaulter() b.defaulter = d b.build() assert.Equal(tt.want, b.rawState) }) } } func Test_stateBuilder_upstream(t *testing.T) { assert := assert.New(t) rand.Seed(42) type fields struct { targetContent *Content currentState *state.KongState } tests := []struct { name string fields fields want *utils.KongRawState }{ { name: "process a non-existent upstream", fields: fields{ targetContent: &Content{ Info: &Info{ SelectorTags: []string{"tag1"}, }, Upstreams: []FUpstream{ { Upstream: kong.Upstream{ Name: kong.String("foo"), Slots: kong.Int(42), }, }, }, }, currentState: existingServiceState(), }, want: &utils.KongRawState{ Upstreams: []*kong.Upstream{ { ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), Name: kong.String("foo"), Slots: kong.Int(42), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Concurrency: kong.Int(10), Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 302}, Interval: kong.Int(0), Successes: kong.Int(0), }, HTTPPath: kong.String("/"), Type: kong.String("http"), Timeout: kong.Int(1), Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), Interval: kong.Int(0), HTTPStatuses: []int{429, 404, 500, 501, 502, 503, 504, 505}, }, }, Passive: &kong.PassiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308}, Successes: kong.Int(0), }, Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 500, 503}, }, }, }, HashOn: kong.String("none"), HashFallback: kong.String("none"), HashOnCookiePath: kong.String("/"), Tags: kong.StringSlice("tag1"), }, }, }, }, { name: "matches ID of an existing service", fields: fields{ targetContent: &Content{ Upstreams: []FUpstream{ { Upstream: kong.Upstream{ Name: kong.String("foo"), }, }, }, }, currentState: existingUpstreamState(), }, want: &utils.KongRawState{ Upstreams: []*kong.Upstream{ { ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), Name: kong.String("foo"), Slots: kong.Int(10000), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Concurrency: kong.Int(10), Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 302}, Interval: kong.Int(0), Successes: kong.Int(0), }, HTTPPath: kong.String("/"), Type: kong.String("http"), Timeout: kong.Int(1), Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), Interval: kong.Int(0), HTTPStatuses: []int{429, 404, 500, 501, 502, 503, 504, 505}, }, }, Passive: &kong.PassiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308}, Successes: kong.Int(0), }, Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 500, 503}, }, }, }, HashOn: kong.String("none"), HashFallback: kong.String("none"), HashOnCookiePath: kong.String("/"), }, }, }, }, { name: "multiple upstreams are handled correctly", fields: fields{ targetContent: &Content{ Upstreams: []FUpstream{ { Upstream: kong.Upstream{ Name: kong.String("foo"), }, }, { Upstream: kong.Upstream{ Name: kong.String("bar"), }, }, }, }, currentState: emptyState(), }, want: &utils.KongRawState{ Upstreams: []*kong.Upstream{ { ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), Name: kong.String("foo"), Slots: kong.Int(10000), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Concurrency: kong.Int(10), Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 302}, Interval: kong.Int(0), Successes: kong.Int(0), }, HTTPPath: kong.String("/"), Type: kong.String("http"), Timeout: kong.Int(1), Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), Interval: kong.Int(0), HTTPStatuses: []int{429, 404, 500, 501, 502, 503, 504, 505}, }, }, Passive: &kong.PassiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308}, Successes: kong.Int(0), }, Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 500, 503}, }, }, }, HashOn: kong.String("none"), HashFallback: kong.String("none"), HashOnCookiePath: kong.String("/"), }, { ID: kong.String("dfd79b4d-7642-4b61-ba0c-9f9f0d3ba55b"), Name: kong.String("bar"), Slots: kong.Int(10000), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Concurrency: kong.Int(10), Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 302}, Interval: kong.Int(0), Successes: kong.Int(0), }, HTTPPath: kong.String("/"), Type: kong.String("http"), Timeout: kong.Int(1), Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), Interval: kong.Int(0), HTTPStatuses: []int{429, 404, 500, 501, 502, 503, 504, 505}, }, }, Passive: &kong.PassiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308}, Successes: kong.Int(0), }, Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 500, 503}, }, }, }, HashOn: kong.String("none"), HashFallback: kong.String("none"), HashOnCookiePath: kong.String("/"), }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &stateBuilder{ targetContent: tt.fields.targetContent, currentState: tt.fields.currentState, } d, _ := utils.GetKongDefaulter() b.defaulter = d b.build() assert.Equal(tt.want, b.rawState) }) } } func Test_stateBuilder(t *testing.T) { assert := assert.New(t) rand.Seed(42) type fields struct { targetContent *Content currentState *state.KongState } tests := []struct { name string fields fields want *utils.KongRawState }{ { name: "end to end test with all entities", fields: fields{ targetContent: &Content{ Info: &Info{ SelectorTags: []string{"tag1"}, }, Services: []FService{ { Service: kong.Service{ Name: kong.String("foo-service"), }, Routes: []*FRoute{ { Route: kong.Route{ Name: kong.String("foo-route1"), }, }, { Route: kong.Route{ ID: kong.String("d125e79a-297c-414b-bc00-ad3a87be6c2b"), Name: kong.String("foo-route2"), }, }, }, }, { Service: kong.Service{ Name: kong.String("bar-service"), }, Routes: []*FRoute{ { Route: kong.Route{ Name: kong.String("bar-route1"), }, }, { Route: kong.Route{ Name: kong.String("bar-route2"), }, }, }, }, { Service: kong.Service{ Name: kong.String("large-payload-service"), }, Routes: []*FRoute{ { Route: kong.Route{ Name: kong.String("dont-buffer-these"), RequestBuffering: kong.Bool(false), ResponseBuffering: kong.Bool(false), }, }, { Route: kong.Route{ Name: kong.String("buffer-these"), RequestBuffering: kong.Bool(true), ResponseBuffering: kong.Bool(true), }, }, }, }, }, Upstreams: []FUpstream{ { Upstream: kong.Upstream{ Name: kong.String("foo"), Slots: kong.Int(42), }, }, }, }, currentState: existingServiceState(), }, want: &utils.KongRawState{ Services: []*kong.Service{ { ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), Name: kong.String("foo-service"), Port: kong.Int(80), Protocol: kong.String("http"), ConnectTimeout: kong.Int(60000), WriteTimeout: kong.Int(60000), ReadTimeout: kong.Int(60000), Tags: kong.StringSlice("tag1"), }, { ID: kong.String("dfd79b4d-7642-4b61-ba0c-9f9f0d3ba55b"), Name: kong.String("bar-service"), Port: kong.Int(80), Protocol: kong.String("http"), ConnectTimeout: kong.Int(60000), WriteTimeout: kong.Int(60000), ReadTimeout: kong.Int(60000), Tags: kong.StringSlice("tag1"), }, { ID: kong.String("9e6f82e5-4e74-4e81-a79e-4bbd6fe34cdc"), Name: kong.String("large-payload-service"), Port: kong.Int(80), Protocol: kong.String("http"), ConnectTimeout: kong.Int(60000), WriteTimeout: kong.Int(60000), ReadTimeout: kong.Int(60000), Tags: kong.StringSlice("tag1"), }, }, Routes: []*kong.Route{ { ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), Name: kong.String("foo-route1"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), Service: &kong.Service{ ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), }, Tags: kong.StringSlice("tag1"), }, { ID: kong.String("d125e79a-297c-414b-bc00-ad3a87be6c2b"), Name: kong.String("foo-route2"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), Service: &kong.Service{ ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), }, Tags: kong.StringSlice("tag1"), }, { ID: kong.String("0cc0d614-4c88-4535-841a-cbe0709b0758"), Name: kong.String("bar-route1"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), Service: &kong.Service{ ID: kong.String("dfd79b4d-7642-4b61-ba0c-9f9f0d3ba55b"), }, Tags: kong.StringSlice("tag1"), }, { ID: kong.String("083f61d3-75bc-42b4-9df4-f91929e18fda"), Name: kong.String("bar-route2"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), Service: &kong.Service{ ID: kong.String("dfd79b4d-7642-4b61-ba0c-9f9f0d3ba55b"), }, Tags: kong.StringSlice("tag1"), }, { ID: kong.String("ba843ee8-d63e-4c4f-be1c-ebea546d8fac"), Name: kong.String("dont-buffer-these"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), Service: &kong.Service{ ID: kong.String("9e6f82e5-4e74-4e81-a79e-4bbd6fe34cdc"), }, Tags: kong.StringSlice("tag1"), RequestBuffering: kong.Bool(false), ResponseBuffering: kong.Bool(false), }, { ID: kong.String("13dd1aac-04ce-4ea2-877c-5579cfa2c78e"), Name: kong.String("buffer-these"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), Service: &kong.Service{ ID: kong.String("9e6f82e5-4e74-4e81-a79e-4bbd6fe34cdc"), }, Tags: kong.StringSlice("tag1"), RequestBuffering: kong.Bool(true), ResponseBuffering: kong.Bool(true), }, }, Upstreams: []*kong.Upstream{ { ID: kong.String("1b0bafae-881b-42a7-9110-8a42ed3c903c"), Name: kong.String("foo"), Slots: kong.Int(42), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Concurrency: kong.Int(10), Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 302}, Interval: kong.Int(0), Successes: kong.Int(0), }, HTTPPath: kong.String("/"), Type: kong.String("http"), Timeout: kong.Int(1), Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), Interval: kong.Int(0), HTTPStatuses: []int{429, 404, 500, 501, 502, 503, 504, 505}, }, }, Passive: &kong.PassiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308}, Successes: kong.Int(0), }, Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 500, 503}, }, }, }, HashOn: kong.String("none"), HashFallback: kong.String("none"), HashOnCookiePath: kong.String("/"), Tags: kong.StringSlice("tag1"), }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &stateBuilder{ targetContent: tt.fields.targetContent, currentState: tt.fields.currentState, } d, _ := utils.GetKongDefaulter() b.defaulter = d b.build() assert.Equal(tt.want, b.rawState) }) } } func Test_stateBuilder_fillPluginConfig(t *testing.T) { type fields struct { targetContent *Content } type args struct { plugin *FPlugin } tests := []struct { name string fields fields args args wantErr bool result FPlugin }{ { name: "nil arg throws an error", wantErr: true, }, { name: "no _plugin_config throws an error", fields: fields{ targetContent: &Content{}, }, args: args{ plugin: &FPlugin{ ConfigSource: kong.String("foo"), }, }, wantErr: true, }, { name: "no _plugin_config throws an error", fields: fields{ targetContent: &Content{ PluginConfigs: map[string]kong.Configuration{ "foo": { "k2": "v3", "k3:": "v3", }, }, }, }, args: args{ plugin: &FPlugin{ ConfigSource: kong.String("foo"), Plugin: kong.Plugin{ Config: kong.Configuration{ "k1": "v1", "k2": "v2", }, }, }, }, result: FPlugin{ ConfigSource: kong.String("foo"), Plugin: kong.Plugin{ Config: kong.Configuration{ "k1": "v1", "k2": "v2", "k3:": "v3", }, }, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := &stateBuilder{ targetContent: tt.fields.targetContent, } if err := b.fillPluginConfig(tt.args.plugin); (err != nil) != tt.wantErr { t.Errorf("stateBuilder.fillPluginConfig() error = %v, wantErr %v", err, tt.wantErr) } if !tt.wantErr && !reflect.DeepEqual(tt.result, tt.args.plugin) { assert.Equal(t, tt.result, *tt.args.plugin) } }) } } deck-1.4.0/file/codegen/000077500000000000000000000000001400603563700147515ustar00rootroot00000000000000deck-1.4.0/file/codegen/.gitignore000066400000000000000000000000101400603563700167300ustar00rootroot00000000000000codegen deck-1.4.0/file/codegen/main.go000066400000000000000000000065171400603563700162350ustar00rootroot00000000000000package main import ( "bytes" "encoding/json" "io/ioutil" "log" "reflect" "text/template" "github.com/alecthomas/jsonschema" "github.com/kong/deck/file" "github.com/kong/go-kong/kong" ) const templateContent = `// Code generated by go generate; DO NOT EDIT. package file const contentSchema = ` + "`{{.Schema}}`\n" type templateData struct { Schema string } var ( // routes and services anyOfNameOrID = []*jsonschema.Type{ { Required: []string{"name"}, }, { Required: []string{"id"}, }, } anyOfUsernameOrID = []*jsonschema.Type{ { Required: []string{"username"}, }, { Required: []string{"id"}, }, } ) func main() { var reflector jsonschema.Reflector reflector.ExpandedStruct = true reflector.TypeMapper = func(typ reflect.Type) *jsonschema.Type { // plugin configuration if typ == reflect.TypeOf(kong.Configuration{}) { return &jsonschema.Type{ Type: "object", Properties: map[string]*jsonschema.Type{}, AdditionalProperties: []byte("true"), } } return nil } schema := reflector.Reflect(file.Content{}) schema.Definitions["Service"].AnyOf = anyOfNameOrID schema.Definitions["FService"].AnyOf = anyOfNameOrID schema.Definitions["Route"].AnyOf = anyOfNameOrID schema.Definitions["FRoute"].AnyOf = anyOfNameOrID schema.Definitions["Consumer"].AnyOf = anyOfUsernameOrID schema.Definitions["FConsumer"].AnyOf = anyOfUsernameOrID schema.Definitions["Upstream"].Required = []string{"name"} schema.Definitions["FUpstream"].Required = []string{"name"} schema.Definitions["FTarget"].Required = []string{"target"} schema.Definitions["FCACertificate"].Required = []string{"cert"} schema.Definitions["FPlugin"].Required = []string{"name"} schema.Definitions["FCertificate"].Required = []string{"id", "cert", "key"} schema.Definitions["FCertificate"].Properties["snis"] = &jsonschema.Type{ Type: "array", Items: &jsonschema.Type{ Type: "object", Properties: map[string]*jsonschema.Type{ "name": { Type: "string", }, }, }, } // creds schema.Definitions["ACLGroup"].Required = []string{"group"} schema.Definitions["BasicAuth"].Required = []string{"username", "password"} schema.Definitions["HMACAuth"].Required = []string{"username", "secret"} schema.Definitions["JWTAuth"].Required = []string{"algorithm", "key", "secret"} schema.Definitions["KeyAuth"].Required = []string{"key"} schema.Definitions["Oauth2Credential"].Required = []string{"name", "client_id", "redirect_uris", "client_secret"} schema.Definitions["MTLSAuth"].Required = []string{"id", "subject_name"} // Foreign references stringType := &jsonschema.Type{Type: "string"} schema.Definitions["FPlugin"].Properties["consumer"] = stringType schema.Definitions["FPlugin"].Properties["service"] = stringType schema.Definitions["FPlugin"].Properties["route"] = stringType schema.Definitions["FService"].Properties["client_certificate"] = stringType jsonSchema, err := json.MarshalIndent(schema, "", " ") if err != nil { log.Fatalln(err) } tmpl := template.New("codegen") tmpl, err = tmpl.Parse(templateContent) if err != nil { log.Fatalln(err) } var buffer bytes.Buffer err = tmpl.Execute(&buffer, templateData{string(jsonSchema)}) if err != nil { log.Fatalln(err) } err = ioutil.WriteFile("schema.go", buffer.Bytes(), 0644) if err != nil { log.Fatalln(err) } } deck-1.4.0/file/reader.go000066400000000000000000000047121400603563700151420ustar00rootroot00000000000000package file import ( "fmt" "github.com/blang/semver" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/pkg/errors" ) // RenderConfig contains necessary information to render a correct // KongConfig from a file. type RenderConfig struct { CurrentState *state.KongState KongVersion semver.Version } // GetContentFromFiles reads in a file with a slice of filenames and constructs // a state. If filename is `-`, then it will read from os.Stdin. // If filename represents a directory, it will traverse the tree // rooted at filename, read all the files with .yaml, .yml and .json extensions // and generate a content after a merge of the content from all the files. // // 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) { if len(filenames) == 0 { return nil, errors.New("filename cannot be empty") } return getContent(filenames) } // Get process the fileContent and renders a RawState. // IDs of entities are matches based on currentState. func Get(fileContent *Content, opt RenderConfig) (*utils.KongRawState, error) { var builder stateBuilder // setup builder.targetContent = fileContent builder.currentState = opt.CurrentState builder.kongVersion = opt.KongVersion d, err := utils.GetKongDefaulter() if err != nil { return nil, errors.Wrap(err, "creating defaulter") } builder.defaulter = d state, err := builder.build() if err != nil { return nil, errors.Wrap(err, "building state") } return state, nil } func ensureJSON(m map[string]interface{}) map[string]interface{} { res := map[string]interface{}{} for k, v := range m { switch v2 := v.(type) { case map[interface{}]interface{}: res[fmt.Sprint(k)] = yamlToJSON(v2) case []interface{}: var array []interface{} for _, element := range v2 { switch el := element.(type) { case map[interface{}]interface{}: array = append(array, yamlToJSON(el)) default: array = append(array, el) } } if array != nil { res[fmt.Sprint(k)] = array } else { res[fmt.Sprint(k)] = v } default: res[fmt.Sprint(k)] = v } } return res } func yamlToJSON(m map[interface{}]interface{}) map[string]interface{} { res := map[string]interface{}{} for k, v := range m { switch v2 := v.(type) { case map[interface{}]interface{}: res[fmt.Sprint(k)] = yamlToJSON(v2) default: res[fmt.Sprint(k)] = v } } return res } deck-1.4.0/file/reader_test.go000066400000000000000000000041761400603563700162050ustar00rootroot00000000000000package file import ( "bytes" "io/ioutil" "os" "reflect" "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func Test_ensureJSON(t *testing.T) { type args struct { m map[string]interface{} } tests := []struct { name string args args want map[string]interface{} }{ { "empty array is kept as is", args{map[string]interface{}{ "foo": []interface{}{}, }}, map[string]interface{}{ "foo": []interface{}{}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := ensureJSON(tt.args.m); !reflect.DeepEqual(got, tt.want) { t.Errorf("ensureJSON() = %v, want %v", got, tt.want) } }) } } func TestReadKongStateFromStdinFailsToParseText(t *testing.T) { var filenames = []string{"-"} assert := assert.New(t) assert.Equal("-", filenames[0]) var content bytes.Buffer content.Write([]byte("hunter2\n")) tmpfile, err := ioutil.TempFile("", "example") if err != nil { panic(err) } defer os.Remove(tmpfile.Name()) if _, err := tmpfile.Write(content.Bytes()); err != nil { panic(err) } if _, err := tmpfile.Seek(0, 0); err != nil { panic(err) } oldStdin := os.Stdin defer func() { os.Stdin = oldStdin }() // Restore original Stdin os.Stdin = tmpfile c, err := GetContentFromFiles(filenames) assert.NotNil(err) assert.Nil(c) } func TestReadKongStateFromStdin(t *testing.T) { var filenames = []string{"-"} assert := assert.New(t) assert.Equal("-", filenames[0]) var content bytes.Buffer content.Write([]byte("services:\n- host: test.com\n name: test service\n")) tmpfile, err := ioutil.TempFile("", "example") if err != nil { panic(err) } defer os.Remove(tmpfile.Name()) if _, err := tmpfile.Write(content.Bytes()); err != nil { panic(err) } if _, err := tmpfile.Seek(0, 0); err != nil { panic(err) } oldStdin := os.Stdin defer func() { os.Stdin = oldStdin }() // Restore original Stdin os.Stdin = tmpfile c, err := GetContentFromFiles(filenames) assert.NotNil(c) assert.Nil(err) assert.Equal(kong.Service{ Name: kong.String("test service"), Host: kong.String("test.com"), }, c.Services[0].Service) } deck-1.4.0/file/readfile.go000066400000000000000000000066011400603563700154520ustar00rootroot00000000000000package file import ( "bufio" "io" "io/ioutil" "os" "path/filepath" "strings" ghodssyaml "github.com/ghodss/yaml" "github.com/imdario/mergo" "github.com/pkg/errors" ) // 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 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, errors.Wrap(err, "reading file") } err = mergo.Merge(&res, content, mergo.WithAppendSlice) if err != nil { return nil, errors.Wrap(err, "merging file contents") } } 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 // 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) { // special case where `-` means stdin if fileOrDir == "-" { return []io.Reader{os.Stdin}, nil } finfo, err := os.Stat(fileOrDir) if err != nil { return nil, errors.Wrap(err, "reading state file") } var files []string if finfo.IsDir() { files, err = configFilesInDir(fileOrDir) if err != nil { return nil, errors.Wrap(err, "getting files from directory") } } else { files = append(files, fileOrDir) } var res []io.Reader for _, file := range files { f, err := os.Open(file) if err != nil { return nil, errors.Wrap(err, "opening file") } res = append(res, bufio.NewReader(f)) } return res, nil } // readContent reads all the byes until io.EOF and unmarshals the read // bytes into Content. func readContent(reader io.Reader) (*Content, error) { var content Content var bytes []byte var err error bytes, err = ioutil.ReadAll(reader) if err != nil { return nil, err } err = validate(bytes) if err != nil { return nil, errors.Wrap(err, "validating file content") } err = yamlUnmarshal(bytes, &content) if err != nil { return nil, err } return &content, nil } // yamlUnmarshal is a wrapper around yaml.Unmarshal to ensure that the right // yaml package is in use. Using ghodss/yaml ensures that no // `map[interface{}]interface{}` is present in go-kong.Plugin.Configuration. // If it is present, then it leads to a silent error. See Github Issue #144. // The verification for this is done using a test. func yamlUnmarshal(bytes []byte, v interface{}) error { return ghodssyaml.Unmarshal(bytes, v) } // configFilesInDir traverses the directory rooted at dir and // returns all the files with a case-insensitive extension of `yml` or `yaml`. func configFilesInDir(dir string) ([]string, error) { var res []string err := filepath.Walk( dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } switch strings.ToLower(filepath.Ext(path)) { case ".yaml", ".yml", ".json": res = append(res, path) } return nil }, ) if err != nil { return nil, errors.Wrap(err, "reading state directory") } return res, nil } deck-1.4.0/file/readfile_test.go000066400000000000000000000163071400603563700165150ustar00rootroot00000000000000package file import ( "io" "os" "reflect" "testing" "github.com/kong/go-kong/kong" ) func Test_configFilesInDir(t *testing.T) { type args struct { dir string } tests := []struct { name string args args want []string wantErr bool }{ { name: "empty directory", args: args{"testdata/emptydir"}, want: nil, wantErr: false, }, { name: "directory does not exist", args: args{"testdata/does-not-exist"}, want: nil, wantErr: true, }, { name: "valid directory", args: args{"testdata/emptyfiles"}, want: []string{ "testdata/emptyfiles/Baz.YamL", "testdata/emptyfiles/bar.yaml", "testdata/emptyfiles/foo.yml", "testdata/emptyfiles/foobar.json", }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := configFilesInDir(tt.args.dir) if (err != nil) != tt.wantErr { t.Errorf("configFilesInDir() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("configFilesInDir() = %v, want %v", got, tt.want) } }) } } func Test_getReaders(t *testing.T) { type args struct { fileOrDir string } tests := []struct { name string args args want []io.Reader // length of returned array wantLen int wantErr bool }{ { name: "read from standard input", args: args{"-"}, want: []io.Reader{os.Stdin}, wantLen: 1, wantErr: false, }, { name: "directory does not exist", args: args{"testdata/does-not-exist"}, want: nil, wantLen: 0, wantErr: true, }, { name: "valid directory", args: args{"testdata/emptyfiles"}, want: nil, wantLen: 4, wantErr: false, }, { name: "valid file", args: args{"testdata/file.yaml"}, want: nil, wantLen: 1, wantErr: false, }, { name: "valid JSON file", args: args{"testdata/file.json"}, want: nil, wantLen: 1, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := getReaders(tt.args.fileOrDir) if (err != nil) != tt.wantErr { t.Errorf("getReaders() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantLen != len(got) { t.Errorf("getReaders() mismatch in returned length: "+ "want = %v, got = %v", tt.wantLen, len(got)) return } if tt.want != nil && !reflect.DeepEqual(got, tt.want) { t.Errorf("getReaders() = %v, want %v", got, tt.want) } }) } } func Test_getContent(t *testing.T) { type args struct { filenames []string } tests := []struct { name string args args want *Content wantErr bool }{ { name: "directory does not exist", args: args{[]string{"testdata/does-not-exist"}}, want: nil, wantErr: true, }, { name: "empty directory", args: args{[]string{"testdata/emptydir"}}, want: &Content{}, wantErr: false, }, { name: "directory with empty files", args: args{[]string{"testdata/emptyfiles"}}, want: &Content{}, wantErr: false, }, { name: "bad yaml", args: args{[]string{"testdata/badyaml"}}, want: nil, wantErr: true, }, { name: "bad JSON", args: args{[]string{"testdata/badjson"}}, want: nil, wantErr: true, }, { name: "single file", args: args{[]string{"testdata/file.yaml"}}, want: &Content{ Services: []FService{ { Service: kong.Service{ Name: kong.String("svc2"), Host: kong.String("2.example.com"), }, Routes: []*FRoute{ { Route: kong.Route{ Name: kong.String("r2"), Paths: kong.StringSlice("/r2"), }, }, }, }, }, Plugins: []FPlugin{ { Plugin: kong.Plugin{ Name: kong.String("prometheus"), }, }, }, }, wantErr: false, }, { name: "multiple files", args: args{[]string{"testdata/file.yaml", "testdata/file.json"}}, want: &Content{ Services: []FService{ { Service: kong.Service{ Name: kong.String("svc2"), Host: kong.String("2.example.com"), }, Routes: []*FRoute{ { Route: kong.Route{ Name: kong.String("r2"), Paths: kong.StringSlice("/r2"), }, }, }, }, }, Plugins: []FPlugin{ { Plugin: kong.Plugin{ Name: kong.String("prometheus"), }, }, }, Consumers: []FConsumer{ { Consumer: kong.Consumer{ Username: kong.String("foo"), }, }, { Consumer: kong.Consumer{ Username: kong.String("bar"), }, }, }, }, wantErr: false, }, { name: "valid directory", args: args{[]string{"testdata/valid"}}, want: &Content{ Info: &Info{ SelectorTags: []string{"tag1"}, }, Services: []FService{ { Service: kong.Service{ Name: kong.String("svc2"), Host: kong.String("2.example.com"), }, Routes: []*FRoute{ { Route: kong.Route{ Name: kong.String("r2"), Paths: kong.StringSlice("/r2"), }, }, }, }, { Service: kong.Service{ Name: kong.String("svc1"), Host: kong.String("1.example.com"), Tags: kong.StringSlice("team-svc1"), }, Routes: []*FRoute{ { Route: kong.Route{ Name: kong.String("r1"), Paths: kong.StringSlice("/r1"), }, }, }, }, }, Consumers: []FConsumer{ { Consumer: kong.Consumer{ Username: kong.String("foo"), }, }, { Consumer: kong.Consumer{ Username: kong.String("bar"), }, }, { Consumer: kong.Consumer{ Username: kong.String("harry"), }, }, }, Plugins: []FPlugin{ { Plugin: kong.Plugin{ Name: kong.String("prometheus"), }, }, }, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := getContent(tt.args.filenames) if (err != nil) != tt.wantErr { t.Errorf("getContent() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("getContent() = %v, want %v", got, tt.want) } }) } } func Test_yamlUnmarshal(t *testing.T) { stringToInterfaceMap := map[string]interface{}{} bytes1 := ` versions: v1: enabled: false ` mapOfMap := map[string]interface{}{} err := yamlUnmarshal([]byte(bytes1), &mapOfMap) if err != nil { t.Errorf("yamlUnmarshal() error = %v (should be nil)", err) } subMap := mapOfMap["versions"] if reflect.TypeOf(subMap) != reflect.TypeOf(stringToInterfaceMap) { t.Errorf("yamlUnmarshal() expected type: %T, got: %T", stringToInterfaceMap, subMap) } bytes2 := ` versions: - enabled: false version: 1 ` mapOfArrayOfMap := map[string]interface{}{} err = yamlUnmarshal([]byte(bytes2), &mapOfArrayOfMap) if err != nil { t.Errorf("yamlUnmarshal() error = %v (should be nil)", err) } array := mapOfArrayOfMap["versions"].([]interface{}) element := array[0] if reflect.TypeOf(element) != reflect.TypeOf(stringToInterfaceMap) { t.Errorf("yamlUnmarshal() expected type: %T, got: %T", stringToInterfaceMap, element) } } deck-1.4.0/file/schema.go000066400000000000000000000657471400603563700151570ustar00rootroot00000000000000// Code generated by go generate; DO NOT EDIT. package file const contentSchema = `{ "$schema": "http://json-schema.org/draft-04/schema#", "properties": { "_format_version": { "type": "string" }, "_info": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/Info" }, "_plugin_configs": { "patternProperties": { ".*": { "additionalProperties": true, "type": "object" } }, "type": "object" }, "_workspace": { "type": "string" }, "ca_certificates": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/FCACertificate" }, "type": "array" }, "certificates": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/FCertificate" }, "type": "array" }, "consumers": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/FConsumer" }, "type": "array" }, "plugins": { "items": { "$ref": "#/definitions/FPlugin" }, "type": "array" }, "routes": { "items": { "$ref": "#/definitions/FRoute" }, "type": "array" }, "services": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/FService" }, "type": "array" }, "upstreams": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/FUpstream" }, "type": "array" } }, "additionalProperties": false, "type": "object", "definitions": { "ACLGroup": { "required": [ "group" ], "properties": { "consumer": { "$ref": "#/definitions/Consumer" }, "created_at": { "type": "integer" }, "group": { "type": "string" }, "id": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "ActiveHealthcheck": { "properties": { "concurrency": { "type": "integer" }, "healthy": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/Healthy" }, "http_path": { "type": "string" }, "https_sni": { "type": "string" }, "https_verify_certificate": { "type": "boolean" }, "timeout": { "type": "integer" }, "type": { "type": "string" }, "unhealthy": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/Unhealthy" } }, "additionalProperties": false, "type": "object" }, "BasicAuth": { "required": [ "username", "password" ], "properties": { "consumer": { "$ref": "#/definitions/Consumer" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "password": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" }, "username": { "type": "string" } }, "additionalProperties": false, "type": "object" }, "CACertificate": { "properties": { "cert": { "type": "string" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "CIDRPort": { "properties": { "ip": { "type": "string" }, "port": { "type": "integer" } }, "additionalProperties": false, "type": "object" }, "Certificate": { "properties": { "cert": { "type": "string" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "key": { "type": "string" }, "snis": { "items": { "type": "string" }, "type": "array" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "Consumer": { "properties": { "created_at": { "type": "integer" }, "custom_id": { "type": "string" }, "id": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" }, "username": { "type": "string" } }, "additionalProperties": false, "type": "object", "anyOf": [ { "required": [ "username" ] }, { "required": [ "id" ] } ] }, "FCACertificate": { "required": [ "cert" ], "properties": { "cert": { "type": "string" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "FCertificate": { "required": [ "id", "cert", "key" ], "properties": { "cert": { "type": "string" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "key": { "type": "string" }, "snis": { "items": { "properties": { "name": { "type": "string" } }, "type": "object" }, "type": "array" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "FConsumer": { "properties": { "acls": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/ACLGroup" }, "type": "array" }, "basicauth_credentials": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/BasicAuth" }, "type": "array" }, "created_at": { "type": "integer" }, "custom_id": { "type": "string" }, "hmacauth_credentials": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/HMACAuth" }, "type": "array" }, "id": { "type": "string" }, "jwt_secrets": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/JWTAuth" }, "type": "array" }, "keyauth_credentials": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/KeyAuth" }, "type": "array" }, "mtls_auth_credentials": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/MTLSAuth" }, "type": "array" }, "oauth2_credentials": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/Oauth2Credential" }, "type": "array" }, "plugins": { "items": { "$ref": "#/definitions/FPlugin" }, "type": "array" }, "tags": { "items": { "type": "string" }, "type": "array" }, "username": { "type": "string" } }, "additionalProperties": false, "type": "object", "anyOf": [ { "required": [ "username" ] }, { "required": [ "id" ] } ] }, "FPlugin": { "required": [ "name" ], "properties": { "_config": { "type": "string" }, "config": { "additionalProperties": true, "type": "object" }, "consumer": { "type": "string" }, "created_at": { "type": "integer" }, "enabled": { "type": "boolean" }, "id": { "type": "string" }, "name": { "type": "string" }, "protocols": { "items": { "type": "string" }, "type": "array" }, "route": { "type": "string" }, "run_on": { "type": "string" }, "service": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "FRoute": { "properties": { "created_at": { "type": "integer" }, "destinations": { "items": { "$ref": "#/definitions/CIDRPort" }, "type": "array" }, "headers": { "patternProperties": { ".*": { "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "hosts": { "items": { "type": "string" }, "type": "array" }, "https_redirect_status_code": { "type": "integer" }, "id": { "type": "string" }, "methods": { "items": { "type": "string" }, "type": "array" }, "name": { "type": "string" }, "path_handling": { "type": "string" }, "paths": { "items": { "type": "string" }, "type": "array" }, "plugins": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/FPlugin" }, "type": "array" }, "preserve_host": { "type": "boolean" }, "protocols": { "items": { "type": "string" }, "type": "array" }, "regex_priority": { "type": "integer" }, "request_buffering": { "type": "boolean" }, "response_buffering": { "type": "boolean" }, "service": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/Service" }, "snis": { "items": { "type": "string" }, "type": "array" }, "sources": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/CIDRPort" }, "type": "array" }, "strip_path": { "type": "boolean" }, "tags": { "items": { "type": "string" }, "type": "array" }, "updated_at": { "type": "integer" } }, "additionalProperties": false, "type": "object", "anyOf": [ { "required": [ "name" ] }, { "required": [ "id" ] } ] }, "FService": { "properties": { "ca_certificates": { "items": { "type": "string" }, "type": "array" }, "client_certificate": { "type": "string" }, "connect_timeout": { "type": "integer" }, "created_at": { "type": "integer" }, "host": { "type": "string" }, "id": { "type": "string" }, "name": { "type": "string" }, "path": { "type": "string" }, "plugins": { "items": { "$ref": "#/definitions/FPlugin" }, "type": "array" }, "port": { "type": "integer" }, "protocol": { "type": "string" }, "read_timeout": { "type": "integer" }, "retries": { "type": "integer" }, "routes": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/FRoute" }, "type": "array" }, "tags": { "items": { "type": "string" }, "type": "array" }, "tls_verify": { "type": "boolean" }, "tls_verify_depth": { "type": "integer" }, "updated_at": { "type": "integer" }, "url": { "type": "string" }, "write_timeout": { "type": "integer" } }, "additionalProperties": false, "type": "object", "anyOf": [ { "required": [ "name" ] }, { "required": [ "id" ] } ] }, "FTarget": { "required": [ "target" ], "properties": { "created_at": { "type": "number" }, "id": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" }, "target": { "type": "string" }, "upstream": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/Upstream" }, "weight": { "type": "integer" } }, "additionalProperties": false, "type": "object" }, "FUpstream": { "required": [ "name" ], "properties": { "algorithm": { "type": "string" }, "client_certificate": { "$ref": "#/definitions/Certificate" }, "created_at": { "type": "integer" }, "hash_fallback": { "type": "string" }, "hash_fallback_header": { "type": "string" }, "hash_on": { "type": "string" }, "hash_on_cookie": { "type": "string" }, "hash_on_cookie_path": { "type": "string" }, "hash_on_header": { "type": "string" }, "healthchecks": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/Healthcheck" }, "host_header": { "type": "string" }, "id": { "type": "string" }, "name": { "type": "string" }, "slots": { "type": "integer" }, "tags": { "items": { "type": "string" }, "type": "array" }, "targets": { "items": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/FTarget" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "HMACAuth": { "required": [ "username", "secret" ], "properties": { "consumer": { "$ref": "#/definitions/Consumer" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "secret": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" }, "username": { "type": "string" } }, "additionalProperties": false, "type": "object" }, "Healthcheck": { "properties": { "active": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/ActiveHealthcheck" }, "passive": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/PassiveHealthcheck" }, "threshold": { "type": "number" } }, "additionalProperties": false, "type": "object" }, "Healthy": { "properties": { "http_statuses": { "items": { "type": "integer" }, "type": "array" }, "interval": { "type": "integer" }, "successes": { "type": "integer" } }, "additionalProperties": false, "type": "object" }, "Info": { "properties": { "select_tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "JWTAuth": { "required": [ "algorithm", "key", "secret" ], "properties": { "algorithm": { "type": "string" }, "consumer": { "$ref": "#/definitions/Consumer" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "key": { "type": "string" }, "rsa_public_key": { "type": "string" }, "secret": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "KeyAuth": { "required": [ "key" ], "properties": { "consumer": { "$ref": "#/definitions/Consumer" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "key": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" }, "ttl": { "type": "integer" } }, "additionalProperties": false, "type": "object" }, "MTLSAuth": { "required": [ "id", "subject_name" ], "properties": { "ca_certificate": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/CACertificate" }, "consumer": { "$ref": "#/definitions/Consumer" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "subject_name": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "Oauth2Credential": { "required": [ "name", "client_id", "redirect_uris", "client_secret" ], "properties": { "client_id": { "type": "string" }, "client_secret": { "type": "string" }, "consumer": { "$ref": "#/definitions/Consumer" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "name": { "type": "string" }, "redirect_uris": { "items": { "type": "string" }, "type": "array" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "PassiveHealthcheck": { "properties": { "healthy": { "$ref": "#/definitions/Healthy" }, "type": { "type": "string" }, "unhealthy": { "$ref": "#/definitions/Unhealthy" } }, "additionalProperties": false, "type": "object" }, "Route": { "properties": { "created_at": { "type": "integer" }, "destinations": { "items": { "$ref": "#/definitions/CIDRPort" }, "type": "array" }, "headers": { "patternProperties": { ".*": { "items": { "type": "string" }, "type": "array" } }, "type": "object" }, "hosts": { "items": { "type": "string" }, "type": "array" }, "https_redirect_status_code": { "type": "integer" }, "id": { "type": "string" }, "methods": { "items": { "type": "string" }, "type": "array" }, "name": { "type": "string" }, "path_handling": { "type": "string" }, "paths": { "items": { "type": "string" }, "type": "array" }, "preserve_host": { "type": "boolean" }, "protocols": { "items": { "type": "string" }, "type": "array" }, "regex_priority": { "type": "integer" }, "request_buffering": { "type": "boolean" }, "response_buffering": { "type": "boolean" }, "service": { "$ref": "#/definitions/Service" }, "snis": { "items": { "type": "string" }, "type": "array" }, "sources": { "items": { "$ref": "#/definitions/CIDRPort" }, "type": "array" }, "strip_path": { "type": "boolean" }, "tags": { "items": { "type": "string" }, "type": "array" }, "updated_at": { "type": "integer" } }, "additionalProperties": false, "type": "object", "anyOf": [ { "required": [ "name" ] }, { "required": [ "id" ] } ] }, "SNI": { "properties": { "certificate": { "$ref": "#/definitions/Certificate" }, "created_at": { "type": "integer" }, "id": { "type": "string" }, "name": { "type": "string" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" }, "Service": { "properties": { "ca_certificates": { "items": { "type": "string" }, "type": "array" }, "client_certificate": { "$ref": "#/definitions/Certificate" }, "connect_timeout": { "type": "integer" }, "created_at": { "type": "integer" }, "host": { "type": "string" }, "id": { "type": "string" }, "name": { "type": "string" }, "path": { "type": "string" }, "port": { "type": "integer" }, "protocol": { "type": "string" }, "read_timeout": { "type": "integer" }, "retries": { "type": "integer" }, "tags": { "items": { "type": "string" }, "type": "array" }, "tls_verify": { "type": "boolean" }, "tls_verify_depth": { "type": "integer" }, "updated_at": { "type": "integer" }, "write_timeout": { "type": "integer" } }, "additionalProperties": false, "type": "object", "anyOf": [ { "required": [ "name" ] }, { "required": [ "id" ] } ] }, "Unhealthy": { "properties": { "http_failures": { "type": "integer" }, "http_statuses": { "items": { "type": "integer" }, "type": "array" }, "interval": { "type": "integer" }, "tcp_failures": { "type": "integer" }, "timeouts": { "type": "integer" } }, "additionalProperties": false, "type": "object" }, "Upstream": { "required": [ "name" ], "properties": { "algorithm": { "type": "string" }, "client_certificate": { "$ref": "#/definitions/Certificate" }, "created_at": { "type": "integer" }, "hash_fallback": { "type": "string" }, "hash_fallback_header": { "type": "string" }, "hash_on": { "type": "string" }, "hash_on_cookie": { "type": "string" }, "hash_on_cookie_path": { "type": "string" }, "hash_on_header": { "type": "string" }, "healthchecks": { "$ref": "#/definitions/Healthcheck" }, "host_header": { "type": "string" }, "id": { "type": "string" }, "name": { "type": "string" }, "slots": { "type": "integer" }, "tags": { "items": { "type": "string" }, "type": "array" } }, "additionalProperties": false, "type": "object" } } }` deck-1.4.0/file/testdata/000077500000000000000000000000001400603563700151565ustar00rootroot00000000000000deck-1.4.0/file/testdata/badjson/000077500000000000000000000000001400603563700165765ustar00rootroot00000000000000deck-1.4.0/file/testdata/badjson/foo.json000066400000000000000000000002101400603563700202450ustar00rootroot00000000000000{ "services": { "foo": "bar" }, "consumers": [ { "username": "foo" }, { "username": "bar" } ] } deck-1.4.0/file/testdata/badyaml/000077500000000000000000000000001400603563700165675ustar00rootroot00000000000000deck-1.4.0/file/testdata/badyaml/bar.yml000066400000000000000000000001531400603563700200550ustar00rootroot00000000000000- name: svc2 host: 2.example.com routes: - name: r2 paths: - /r2 plugins: - name: prometheus deck-1.4.0/file/testdata/emptydir/000077500000000000000000000000001400603563700170135ustar00rootroot00000000000000deck-1.4.0/file/testdata/emptydir/README000066400000000000000000000000331400603563700176670ustar00rootroot00000000000000Keep this directory empty. deck-1.4.0/file/testdata/emptyfiles/000077500000000000000000000000001400603563700173375ustar00rootroot00000000000000deck-1.4.0/file/testdata/emptyfiles/Baz.YamL000066400000000000000000000000001400603563700206250ustar00rootroot00000000000000deck-1.4.0/file/testdata/emptyfiles/bar.yaml000066400000000000000000000000001400603563700207550ustar00rootroot00000000000000deck-1.4.0/file/testdata/emptyfiles/foo.notyaml000066400000000000000000000000001400603563700215150ustar00rootroot00000000000000deck-1.4.0/file/testdata/emptyfiles/foo.yaml.pdf000066400000000000000000000000001400603563700215440ustar00rootroot00000000000000deck-1.4.0/file/testdata/emptyfiles/foo.yml000066400000000000000000000000001400603563700206330ustar00rootroot00000000000000deck-1.4.0/file/testdata/emptyfiles/foobar.json000066400000000000000000000000001400603563700214700ustar00rootroot00000000000000deck-1.4.0/file/testdata/emptyfiles/not-a-file.yaml/000077500000000000000000000000001400603563700222335ustar00rootroot00000000000000deck-1.4.0/file/testdata/emptyfiles/not-a-file.yaml/info.txt000066400000000000000000000000001400603563700237150ustar00rootroot00000000000000deck-1.4.0/file/testdata/file.json000066400000000000000000000001421400603563700167650ustar00rootroot00000000000000{ "consumers": [ { "username": "foo" }, { "username": "bar" } ] } deck-1.4.0/file/testdata/file.yaml000066400000000000000000000001651400603563700167630ustar00rootroot00000000000000services: - name: svc2 host: 2.example.com routes: - name: r2 paths: - /r2 plugins: - name: prometheus deck-1.4.0/file/testdata/valid/000077500000000000000000000000001400603563700162555ustar00rootroot00000000000000deck-1.4.0/file/testdata/valid/bar.yml000066400000000000000000000001651400603563700175460ustar00rootroot00000000000000services: - name: svc2 host: 2.example.com routes: - name: r2 paths: - /r2 plugins: - name: prometheus deck-1.4.0/file/testdata/valid/consumers.json000066400000000000000000000001421400603563700211630ustar00rootroot00000000000000{ "consumers": [ { "username": "foo" }, { "username": "bar" } ] } deck-1.4.0/file/testdata/valid/foo.yaml000066400000000000000000000002531400603563700177240ustar00rootroot00000000000000_info: select_tags: - tag1 services: - name: svc1 host: 1.example.com tags: - team-svc1 routes: - name: r1 paths: - /r1 consumers: - username: harry deck-1.4.0/file/types.go000066400000000000000000000333251400603563700150460ustar00rootroot00000000000000package file import ( "encoding/json" "net/url" "strconv" "strings" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" "github.com/pkg/errors" ) // Format is a file format for Kong's configuration. type Format string type id interface { id() string } const ( // JSON is JSON file format. JSON = "JSON" // YAML if YAML file format. YAML = "YAML" ) // FService represents a Kong Service and it's associated routes and plugins. type FService struct { kong.Service Routes []*FRoute `json:"routes,omitempty" yaml:",omitempty"` Plugins []*FPlugin `json:"plugins,omitempty" yaml:",omitempty"` // sugar property URL *string `json:"url,omitempty" yaml:",omitempty"` } // id is used for sorting. func (s FService) id() string { if s.ID != nil { return *s.ID } if s.Name != nil { return *s.Name } return "" } type service struct { ClientCertificate *string `json:"client_certificate,omitempty" yaml:"client_certificate,omitempty"` ConnectTimeout *int `json:"connect_timeout,omitempty" yaml:"connect_timeout,omitempty"` CreatedAt *int `json:"created_at,omitempty" yaml:"created_at,omitempty"` Host *string `json:"host,omitempty" yaml:"host,omitempty"` ID *string `json:"id,omitempty" yaml:"id,omitempty"` Name *string `json:"name,omitempty" yaml:"name,omitempty"` Path *string `json:"path,omitempty" yaml:"path,omitempty"` Port *int `json:"port,omitempty" yaml:"port,omitempty"` Protocol *string `json:"protocol,omitempty" yaml:"protocol,omitempty"` ReadTimeout *int `json:"read_timeout,omitempty" yaml:"read_timeout,omitempty"` Retries *int `json:"retries,omitempty" yaml:"retries,omitempty"` UpdatedAt *int `json:"updated_at,omitempty" yaml:"updated_at,omitempty"` WriteTimeout *int `json:"write_timeout,omitempty" yaml:"write_timeout,omitempty"` Tags []*string `json:"tags,omitempty" yaml:"tags,omitempty"` TLSVerify *bool `json:"tls_verify,omitempty" yaml:"tls_verify,omitempty"` TLSVerifyDepth *int `json:"tls_verify_depth,omitempty" yaml:"tls_verify_depth,omitempty"` CACertificates []*string `json:"ca_certificates,omitempty" yaml:"ca_certificates,omitempty"` Routes []*FRoute `json:"routes,omitempty" yaml:",omitempty"` Plugins []*FPlugin `json:"plugins,omitempty" yaml:",omitempty"` // sugar property URL *string `json:"url,omitempty" yaml:",omitempty"` } func copyToService(fService FService) service { s := service{} if fService.ClientCertificate != nil && !utils.Empty(fService.ClientCertificate.ID) { s.ClientCertificate = kong.String(*fService.ClientCertificate.ID) } s.CACertificates = fService.CACertificates s.TLSVerify = fService.TLSVerify s.TLSVerifyDepth = fService.TLSVerifyDepth s.ConnectTimeout = fService.ConnectTimeout s.CreatedAt = fService.CreatedAt s.Host = fService.Host s.ID = fService.ID s.Name = fService.Name s.Path = fService.Path s.Port = fService.Port s.Protocol = fService.Protocol s.ReadTimeout = fService.ReadTimeout s.Retries = fService.Retries s.UpdatedAt = fService.UpdatedAt s.WriteTimeout = fService.WriteTimeout s.Tags = fService.Tags s.Routes = fService.Routes s.Plugins = fService.Plugins return s } func unwrapURL(urlString string, fService *FService) error { parsed, err := url.Parse(urlString) if err != nil { return errors.New("invaid url: " + urlString) } if parsed.Scheme == "" { return errors.New("invalid url:" + urlString) } fService.Protocol = kong.String(parsed.Scheme) fService.Port = kong.Int(80) if parsed.Scheme == "https" { fService.Port = kong.Int(443) } if parsed.Host != "" { hostPort := strings.Split(parsed.Host, ":") fService.Host = kong.String(hostPort[0]) if len(hostPort) > 1 { port, err := strconv.Atoi(hostPort[1]) if err == nil { fService.Port = kong.Int(port) } } } if parsed.Path != "" { fService.Path = kong.String(parsed.Path) } return nil } func copyFromService(service service, fService *FService) error { if service.ClientCertificate != nil && !utils.Empty(service.ClientCertificate) { fService.ClientCertificate = &kong.Certificate{ ID: kong.String(*service.ClientCertificate), } } if !utils.Empty(service.URL) { err := unwrapURL(*service.URL, fService) if err != nil { return err } } fService.ConnectTimeout = service.ConnectTimeout fService.CreatedAt = service.CreatedAt fService.ID = service.ID fService.Name = service.Name if service.Protocol != nil { fService.Protocol = service.Protocol } if service.Host != nil { fService.Host = service.Host } if service.Port != nil { fService.Port = service.Port } if service.Path != nil { fService.Path = service.Path } fService.ReadTimeout = service.ReadTimeout fService.Retries = service.Retries fService.UpdatedAt = service.UpdatedAt fService.WriteTimeout = service.WriteTimeout fService.Tags = service.Tags fService.CACertificates = service.CACertificates fService.TLSVerify = service.TLSVerify fService.TLSVerifyDepth = service.TLSVerifyDepth fService.Routes = service.Routes fService.Plugins = service.Plugins return nil } // MarshalYAML is a custom marshal to handle // SNI. func (s FService) MarshalYAML() (interface{}, error) { return copyToService(s), nil } // UnmarshalYAML is a custom marshal method to handle // foreign references. func (s *FService) UnmarshalYAML(unmarshal func(interface{}) error) error { var service service if err := unmarshal(&service); err != nil { return err } return copyFromService(service, s) } // MarshalJSON is a custom marshal method to handle // foreign references. func (s FService) MarshalJSON() ([]byte, error) { service := copyToService(s) return json.Marshal(service) } // UnmarshalJSON is a custom marshal method to handle // foreign references. func (s *FService) UnmarshalJSON(b []byte) error { var service service err := json.Unmarshal(b, &service) if err != nil { return err } return copyFromService(service, s) } // FRoute represents a Kong Route and it's associated plugins. type FRoute struct { kong.Route `yaml:",inline,omitempty"` Plugins []*FPlugin `json:"plugins,omitempty" yaml:",omitempty"` } // id is used for sorting. func (r FRoute) id() string { if r.ID != nil { return *r.ID } if r.Name != nil { return *r.Name } return "" } // FUpstream represents a Kong Upstream and it's associated targets. type FUpstream struct { kong.Upstream `yaml:",inline,omitempty"` Targets []*FTarget `json:"targets,omitempty" yaml:",omitempty"` } // id is used for sorting. func (u FUpstream) id() string { if u.ID != nil { return *u.ID } if u.Name != nil { return *u.Name } return "" } // FTarget represents a Kong Target. type FTarget struct { kong.Target `yaml:",inline,omitempty"` } // id is used for sorting. func (t FTarget) id() string { if t.ID != nil { return *t.ID } if t.Target.Target != nil { return *t.Target.Target } return "" } // FCertificate represents a Kong Certificate. type FCertificate struct { ID *string `json:"id,omitempty" yaml:"id,omitempty"` Cert *string `json:"cert,omitempty" yaml:"cert,omitempty"` Key *string `json:"key,omitempty" yaml:"key,omitempty"` CreatedAt *int64 `json:"created_at,omitempty" yaml:"created_at,omitempty"` Tags []*string `json:"tags,omitempty" yaml:"tags,omitempty"` SNIs []kong.SNI `json:"snis,omitempty" yaml:"snis,omitempty"` } // id is used for sorting. func (c FCertificate) id() string { if c.ID != nil { return *c.ID } if c.Cert != nil { return *c.Cert } return "" } // FCACertificate represents a Kong CACertificate. type FCACertificate struct { kong.CACertificate `yaml:",inline,omitempty"` } // id is used for sorting. func (c FCACertificate) id() string { if c.ID != nil { return *c.ID } if c.Cert != nil { return *c.Cert } return "" } // FPlugin represents a plugin in Kong. type FPlugin struct { kong.Plugin `yaml:",inline,omitempty"` ConfigSource *string `json:"_config,omitempty" yaml:"_config,omitempty"` } // 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"` 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"` Protocols []*string `json:"protocols,omitempty" yaml:"protocols,omitempty"` Tags []*string `json:"tags,omitempty" yaml:"tags,omitempty"` ConfigSource *string `json:"_config,omitempty" yaml:"_config,omitempty"` } func copyToFoo(p FPlugin) foo { f := foo{} if p.ID != nil { f.ID = p.ID } if p.Name != nil { f.Name = p.Name } if p.Enabled != nil { f.Enabled = p.Enabled } if p.RunOn != nil { f.RunOn = p.RunOn } if p.Protocols != nil { f.Protocols = p.Protocols } if p.Tags != nil { f.Tags = p.Tags } if p.Config != nil { f.Config = p.Config } if p.ConfigSource != nil { f.ConfigSource = p.ConfigSource } if p.Plugin.Consumer != nil { f.Consumer = *p.Plugin.Consumer.ID } if p.Plugin.Route != nil { f.Route = *p.Plugin.Route.ID } if p.Plugin.Service != nil { f.Service = *p.Plugin.Service.ID } return f } func copyFromFoo(f foo, p *FPlugin) { if f.ID != nil { p.ID = f.ID } if f.Name != nil { p.Name = f.Name } if f.Enabled != nil { p.Enabled = f.Enabled } if f.RunOn != nil { p.RunOn = f.RunOn } if f.Protocols != nil { p.Protocols = f.Protocols } if f.Tags != nil { p.Tags = f.Tags } if f.Config != nil { p.Config = f.Config } if f.ConfigSource != nil { p.ConfigSource = f.ConfigSource } if f.Consumer != "" { p.Consumer = &kong.Consumer{ ID: kong.String(f.Consumer), } } if f.Route != "" { p.Route = &kong.Route{ ID: kong.String(f.Route), } } if f.Service != "" { p.Service = &kong.Service{ ID: kong.String(f.Service), } } } // MarshalYAML is a custom marshal method to handle // foreign references. func (p FPlugin) MarshalYAML() (interface{}, error) { return copyToFoo(p), nil } // UnmarshalYAML is a custom marshal method to handle // foreign references. func (p *FPlugin) UnmarshalYAML(unmarshal func(interface{}) error) error { var f foo if err := unmarshal(&f); err != nil { return err } copyFromFoo(f, p) return nil } // MarshalJSON is a custom marshal method to handle // foreign references. func (p FPlugin) MarshalJSON() ([]byte, error) { f := copyToFoo(p) return json.Marshal(f) } // UnmarshalJSON is a custom marshal method to handle // foreign references. func (p *FPlugin) UnmarshalJSON(b []byte) error { var f foo err := json.Unmarshal(b, &f) if err != nil { return err } copyFromFoo(f, p) return nil } // id is used for sorting. func (p FPlugin) id() string { if p.ID != nil { return *p.ID } // concat plugin name and foreign relations key := "" key = *p.Name if p.Consumer != nil { key += *p.Consumer.ID } if p.Route != nil { key += *p.Route.ID } if p.Service != nil { key += *p.Service.ID } return key } // FConsumer represents a consumer in Kong. type FConsumer struct { kong.Consumer `yaml:",inline,omitempty"` Plugins []*FPlugin `json:"plugins,omitempty" yaml:",omitempty"` KeyAuths []*kong.KeyAuth `json:"keyauth_credentials,omitempty" yaml:"keyauth_credentials,omitempty"` HMACAuths []*kong.HMACAuth `json:"hmacauth_credentials,omitempty" yaml:"hmacauth_credentials,omitempty"` JWTAuths []*kong.JWTAuth `json:"jwt_secrets,omitempty" yaml:"jwt_secrets,omitempty"` BasicAuths []*kong.BasicAuth `json:"basicauth_credentials,omitempty" yaml:"basicauth_credentials,omitempty"` Oauth2Creds []*kong.Oauth2Credential `json:"oauth2_credentials,omitempty" yaml:"oauth2_credentials,omitempty"` ACLGroups []*kong.ACLGroup `json:"acls,omitempty" yaml:"acls,omitempty"` MTLSAuths []*kong.MTLSAuth `json:"mtls_auth_credentials,omitempty" yaml:"mtls_auth_credentials,omitempty"` } // id is used for sorting. func (c FConsumer) id() string { if c.ID != nil { return *c.ID } if c.Username != nil { return *c.Username } return "" } // Info contains meta-data of the file. type Info struct { SelectorTags []string `json:"select_tags,omitempty" yaml:"select_tags,omitempty"` } //go:generate go run ./codegen/main.go // Content represents a serialized Kong state. type Content struct { FormatVersion string `json:"_format_version,omitempty" yaml:"_format_version,omitempty"` Info *Info `json:"_info,omitempty" yaml:"_info,omitempty"` Workspace string `json:"_workspace,omitempty" yaml:"_workspace,omitempty"` Services []FService `json:"services,omitempty" yaml:",omitempty"` Routes []FRoute `json:"routes,omitempty" yaml:",omitempty"` Consumers []FConsumer `json:"consumers,omitempty" yaml:",omitempty"` Plugins []FPlugin `json:"plugins,omitempty" yaml:",omitempty"` Upstreams []FUpstream `json:"upstreams,omitempty" yaml:",omitempty"` Certificates []FCertificate `json:"certificates,omitempty" yaml:",omitempty"` CACertificates []FCACertificate `json:"ca_certificates,omitempty" yaml:"ca_certificates,omitempty"` PluginConfigs map[string]kong.Configuration `json:"_plugin_configs,omitempty" yaml:"_plugin_configs,omitempty"` } deck-1.4.0/file/types_test.go000066400000000000000000000117711400603563700161060ustar00rootroot00000000000000package file import ( "encoding/json" "fmt" "reflect" "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" yaml "gopkg.in/yaml.v2" ) var ( jsonString = `{ "name": "rate-limiting", "config": { "minute": 10 }, "service": "foo", "route": "bar", "consumer": "baz", "enabled": true, "run_on": "first", "protocols": [ "http" ] }` yamlString = ` name: rate-limiting config: minute: 10 service: foo consumer: baz route: bar enabled: true run_on: first protocols: - http ` ) func TestPluginUnmarshalYAML(t *testing.T) { var p FPlugin assert := assert.New(t) assert.Nil(yaml.Unmarshal([]byte(yamlString), &p)) assert.Equal(kong.Plugin{ Name: p.Name, Config: p.Config, Enabled: p.Enabled, RunOn: p.RunOn, Protocols: p.Protocols, Service: &kong.Service{ ID: kong.String("foo"), }, Consumer: &kong.Consumer{ ID: kong.String("baz"), }, Route: &kong.Route{ ID: kong.String("bar"), }, }, p.Plugin) } func TestPluginUnmarshalJSON(t *testing.T) { var p FPlugin assert := assert.New(t) assert.Nil(json.Unmarshal([]byte(jsonString), &p)) assert.Equal(kong.Plugin{ Name: p.Name, Config: p.Config, Enabled: p.Enabled, RunOn: p.RunOn, Protocols: p.Protocols, Service: &kong.Service{ ID: kong.String("foo"), }, Consumer: &kong.Consumer{ ID: kong.String("baz"), }, Route: &kong.Route{ ID: kong.String("bar"), }, }, p.Plugin) } func Test_unwrapURL(t *testing.T) { type args struct { urlString string fService *FService } tests := []struct { name string args args wantErr bool }{ { args: args{ urlString: "https://foo.com:8008/bar", fService: &FService{ Service: kong.Service{ Host: kong.String("foo.com"), Port: kong.Int(8008), Protocol: kong.String("https"), Path: kong.String("/bar"), }, }, }, wantErr: false, }, { args: args{ urlString: "https://foo.com/bar", fService: &FService{ Service: kong.Service{ Host: kong.String("foo.com"), Protocol: kong.String("https"), Path: kong.String("/bar"), Port: kong.Int(443), }, }, }, wantErr: false, }, { args: args{ urlString: "https://foo.com:4224/", fService: &FService{ Service: kong.Service{ Host: kong.String("foo.com"), Protocol: kong.String("https"), Path: kong.String("/"), Port: kong.Int(4224), }, }, }, wantErr: false, }, { args: args{ urlString: "https://foo.com/", fService: &FService{ Service: kong.Service{ Host: kong.String("foo.com"), Protocol: kong.String("https"), Path: kong.String("/"), Port: kong.Int(443), }, }, }, wantErr: false, }, { args: args{ urlString: "http://foo.com:4242", fService: &FService{ Service: kong.Service{ Host: kong.String("foo.com"), Protocol: kong.String("http"), Port: kong.Int(4242), }, }, }, wantErr: false, }, { args: args{ urlString: "http://foo.com", fService: &FService{ Service: kong.Service{ Host: kong.String("foo.com"), Protocol: kong.String("http"), Port: kong.Int(80), }, }, }, wantErr: false, }, { args: args{ urlString: "grpc://foocom", fService: &FService{ Service: kong.Service{ Host: kong.String("foocom"), Protocol: kong.String("grpc"), Port: kong.Int(80), }, }, }, wantErr: false, }, { args: args{ urlString: "foo.com/sdf", fService: &FService{ Service: kong.Service{}, }, }, wantErr: true, }, { args: args{ urlString: "foo.com", fService: &FService{ Service: kong.Service{}, }, }, wantErr: true, }, { args: args{ urlString: "42:", fService: &FService{ Service: kong.Service{}, }, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { in := FService{} if err := unwrapURL(tt.args.urlString, &in); (err != nil) != tt.wantErr { t.Errorf("unwrapURL() error = %v, wantErr %v", err, tt.wantErr) } fmt.Printf("\n\n%+v", in) if !reflect.DeepEqual(tt.args.fService, &in) { t.Errorf("unwrapURL() got = %v, want = %v", &in, tt.args.fService) } }) } } deck-1.4.0/file/validate.go000066400000000000000000000013561400603563700154720ustar00rootroot00000000000000package file import ( yaml "github.com/ghodss/yaml" "github.com/kong/deck/utils" "github.com/pkg/errors" "github.com/xeipuuv/gojsonschema" ) func validate(content []byte) error { var c map[string]interface{} err := yaml.Unmarshal(content, &c) if err != nil { return errors.Wrap(err, "unmarshaling file content") } c = ensureJSON(c) schemaLoader := gojsonschema.NewStringLoader(contentSchema) documentLoader := gojsonschema.NewGoLoader(c) result, err := gojsonschema.Validate(schemaLoader, documentLoader) if err != nil { return err } if result.Valid() { return nil } var errs utils.ErrArray for _, desc := range result.Errors() { err := errors.New(desc.String()) errs.Errors = append(errs.Errors, err) } return errs } deck-1.4.0/file/writer.go000066400000000000000000000264361400603563700152230ustar00rootroot00000000000000package file import ( "encoding/json" "fmt" "io/ioutil" "path/filepath" "reflect" "sort" "strings" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/pkg/errors" yaml "gopkg.in/yaml.v2" ) // WriteConfig holds settings to use to write the state file. type WriteConfig struct { Workspace string SelectTags []string Filename string FileFormat Format WithID bool } func compareID(obj1, obj2 id) bool { return strings.Compare(obj1.id(), obj2.id()) < 0 } // KongStateToFile writes a state object to file with filename. // It will omit timestamps and IDs while writing. func KongStateToFile(kongState *state.KongState, config WriteConfig) error { // TODO break-down this giant function var file Content file.Workspace = config.Workspace // hardcoded as only one version exists currently file.FormatVersion = "1.1" selectTags := config.SelectTags if len(selectTags) > 0 { file.Info = &Info{ SelectorTags: selectTags, } } services, err := kongState.Services.GetAll() if err != nil { return err } for _, s := range services { s := FService{Service: s.Service} routes, err := kongState.Routes.GetAllByServiceID(*s.ID) if err != nil { return err } plugins, err := kongState.Plugins.GetAllByServiceID(*s.ID) if err != nil { return err } for _, p := range plugins { if p.Route != nil || p.Consumer != nil { continue } p.Service = nil zeroOutID(p, p.Name, config.WithID) zeroOutTimestamps(p) utils.MustRemoveTags(&p.Plugin, selectTags) s.Plugins = append(s.Plugins, &FPlugin{Plugin: p.Plugin}) } sort.SliceStable(s.Plugins, func(i, j int) bool { return compareID(s.Plugins[i], s.Plugins[j]) }) for _, r := range routes { plugins, err := kongState.Plugins.GetAllByRouteID(*r.ID) if err != nil { return err } r.Service = nil zeroOutID(r, r.Name, config.WithID) zeroOutTimestamps(r) utils.MustRemoveTags(&r.Route, selectTags) route := &FRoute{Route: r.Route} for _, p := range plugins { if p.Service != nil || p.Consumer != nil { continue } p.Route = nil zeroOutID(p, p.Name, config.WithID) zeroOutTimestamps(p) utils.MustRemoveTags(&p.Plugin, selectTags) route.Plugins = append(route.Plugins, &FPlugin{Plugin: p.Plugin}) } sort.SliceStable(route.Plugins, func(i, j int) bool { return compareID(route.Plugins[i], route.Plugins[j]) }) s.Routes = append(s.Routes, route) } sort.SliceStable(s.Routes, func(i, j int) bool { return compareID(s.Routes[i], s.Routes[j]) }) zeroOutID(&s, s.Name, config.WithID) zeroOutTimestamps(&s) utils.MustRemoveTags(&s.Service, selectTags) file.Services = append(file.Services, s) } sort.SliceStable(file.Services, func(i, j int) bool { return compareID(file.Services[i], file.Services[j]) }) // service-less routes routes, err := kongState.Routes.GetAll() if err != nil { return err } for _, r := range routes { if r.Service != nil { continue } plugins, err := kongState.Plugins.GetAllByRouteID(*r.ID) if err != nil { return err } zeroOutID(r, r.Name, config.WithID) zeroOutTimestamps(r) utils.MustRemoveTags(&r.Route, selectTags) route := &FRoute{Route: r.Route} for _, p := range plugins { if p.Service != nil || p.Consumer != nil { continue } p.Route = nil zeroOutID(p, p.Name, config.WithID) zeroOutTimestamps(p) utils.MustRemoveTags(&p.Plugin, selectTags) route.Plugins = append(route.Plugins, &FPlugin{Plugin: p.Plugin}) } sort.SliceStable(route.Plugins, func(i, j int) bool { return compareID(route.Plugins[i], route.Plugins[j]) }) file.Routes = append(file.Routes, *route) } sort.SliceStable(file.Routes, func(i, j int) bool { return compareID(file.Routes[i], file.Routes[j]) }) // Add global and multi-relational plugins plugins, err := kongState.Plugins.GetAll() if err != nil { return err } for _, p := range plugins { associations := 0 if p.Consumer != nil { associations++ cID := *p.Consumer.ID consumer, err := kongState.Consumers.Get(cID) if err != nil { return err } if !utils.Empty(consumer.Username) { cID = *consumer.Username } p.Consumer.ID = &cID } if p.Service != nil { associations++ sID := *p.Service.ID service, err := kongState.Services.Get(sID) if err != nil { return err } if !utils.Empty(service.Name) { sID = *service.Name } p.Service.ID = &sID } if p.Route != nil { associations++ rID := *p.Route.ID route, err := kongState.Routes.Get(rID) if err != nil { return err } if !utils.Empty(route.Name) { rID = *route.Name } p.Route.ID = &rID } if associations == 0 || associations > 1 { zeroOutID(p, p.Name, config.WithID) zeroOutTimestamps(p) utils.MustRemoveTags(&p.Plugin, selectTags) p := FPlugin{Plugin: p.Plugin} file.Plugins = append(file.Plugins, p) } } sort.SliceStable(file.Plugins, func(i, j int) bool { return compareID(file.Plugins[i], file.Plugins[j]) }) upstreams, err := kongState.Upstreams.GetAll() if err != nil { return err } for _, u := range upstreams { u := FUpstream{Upstream: u.Upstream} targets, err := kongState.Targets.GetAllByUpstreamID(*u.ID) if err != nil { return err } for _, t := range targets { t.Upstream = nil zeroOutID(t, t.Target.Target, config.WithID) zeroOutTimestamps(t) utils.MustRemoveTags(&t.Target, selectTags) u.Targets = append(u.Targets, &FTarget{Target: t.Target}) } sort.SliceStable(u.Targets, func(i, j int) bool { return compareID(u.Targets[i], u.Targets[j]) }) zeroOutID(&u, u.Name, config.WithID) zeroOutTimestamps(&u) utils.MustRemoveTags(&u.Upstream, selectTags) file.Upstreams = append(file.Upstreams, u) } sort.SliceStable(file.Upstreams, func(i, j int) bool { return compareID(file.Upstreams[i], file.Upstreams[j]) }) certificates, err := kongState.Certificates.GetAll() if err != nil { return err } for _, c := range certificates { c := FCertificate{ ID: c.ID, Cert: c.Cert, Key: c.Key, Tags: c.Tags, } snis, err := kongState.SNIs.GetAllByCertID(*c.ID) if err != nil { return err } for _, s := range snis { s.Certificate = nil zeroOutID(s, s.Name, config.WithID) zeroOutTimestamps(s) utils.MustRemoveTags(&s.SNI, selectTags) c.SNIs = append(c.SNIs, s.SNI) } sort.SliceStable(c.SNIs, func(i, j int) bool { return strings.Compare(*c.SNIs[i].Name, *c.SNIs[j].Name) < 0 }) zeroOutTimestamps(&c) utils.MustRemoveTags(&c, selectTags) file.Certificates = append(file.Certificates, c) } sort.SliceStable(file.Certificates, func(i, j int) bool { return compareID(file.Certificates[i], file.Certificates[j]) }) caCertificates, err := kongState.CACertificates.GetAll() if err != nil { return err } for _, c := range caCertificates { c := FCACertificate{CACertificate: c.CACertificate} zeroOutTimestamps(&c) utils.MustRemoveTags(&c.CACertificate, selectTags) file.CACertificates = append(file.CACertificates, c) } sort.SliceStable(file.CACertificates, func(i, j int) bool { return compareID(file.CACertificates[i], file.CACertificates[j]) }) consumers, err := kongState.Consumers.GetAll() if err != nil { return err } for _, c := range consumers { c := FConsumer{Consumer: c.Consumer} plugins, err := kongState.Plugins.GetAllByConsumerID(*c.ID) if err != nil { return err } for _, p := range plugins { if p.Service != nil || p.Route != nil { continue } zeroOutID(p, p.Name, config.WithID) zeroOutTimestamps(p) p.Consumer = nil utils.MustRemoveTags(&p.Plugin, selectTags) c.Plugins = append(c.Plugins, &FPlugin{Plugin: p.Plugin}) } sort.SliceStable(c.Plugins, func(i, j int) bool { return compareID(c.Plugins[i], c.Plugins[j]) }) // custom-entities associated with Consumer keyAuths, err := kongState.KeyAuths.GetAllByConsumerID(*c.ID) if err != nil { return err } for _, k := range keyAuths { zeroOutID(k, k.Key, config.WithID) zeroOutTimestamps(k) k.Consumer = nil c.KeyAuths = append(c.KeyAuths, &k.KeyAuth) } hmacAuth, err := kongState.HMACAuths.GetAllByConsumerID(*c.ID) if err != nil { return err } for _, k := range hmacAuth { k.Consumer = nil zeroOutID(k, k.Username, config.WithID) zeroOutTimestamps(k) c.HMACAuths = append(c.HMACAuths, &k.HMACAuth) } jwtSecrets, err := kongState.JWTAuths.GetAllByConsumerID(*c.ID) if err != nil { return err } for _, k := range jwtSecrets { k.Consumer = nil zeroOutID(k, k.Key, config.WithID) zeroOutTimestamps(k) c.JWTAuths = append(c.JWTAuths, &k.JWTAuth) } basicAuths, err := kongState.BasicAuths.GetAllByConsumerID(*c.ID) if err != nil { return err } for _, k := range basicAuths { k.Consumer = nil zeroOutID(k, k.Username, config.WithID) zeroOutTimestamps(k) c.BasicAuths = append(c.BasicAuths, &k.BasicAuth) } oauth2Creds, err := kongState.Oauth2Creds.GetAllByConsumerID(*c.ID) if err != nil { return err } for _, k := range oauth2Creds { k.Consumer = nil zeroOutID(k, k.ClientID, config.WithID) zeroOutTimestamps(k) c.Oauth2Creds = append(c.Oauth2Creds, &k.Oauth2Credential) } aclGroups, err := kongState.ACLGroups.GetAllByConsumerID(*c.ID) if err != nil { return err } for _, k := range aclGroups { k.Consumer = nil zeroOutID(k, k.Group, config.WithID) zeroOutTimestamps(k) c.ACLGroups = append(c.ACLGroups, &k.ACLGroup) } mtlsAuths, err := kongState.MTLSAuths.GetAllByConsumerID(*c.ID) if err != nil { return err } for _, k := range mtlsAuths { zeroOutTimestamps(k) k.Consumer = nil c.MTLSAuths = append(c.MTLSAuths, &k.MTLSAuth) } zeroOutID(&c, c.Username, config.WithID) zeroOutTimestamps(&c) utils.MustRemoveTags(&c.Consumer, selectTags) file.Consumers = append(file.Consumers, c) } sort.SliceStable(file.Consumers, func(i, j int) bool { return compareID(file.Consumers[i], file.Consumers[j]) }) return writeFile(file, config.Filename, config.FileFormat) } func writeFile(content Content, filename string, format Format) error { var c []byte var err error switch format { case YAML: c, err = yaml.Marshal(content) if err != nil { return err } case JSON: c, err = json.MarshalIndent(content, "", " ") if err != nil { return err } default: return errors.New("unknown file format: " + string(format)) } if filename == "-" { _, err = fmt.Print(string(c)) } else { filename = addExtToFilename(filename, string(format)) err = ioutil.WriteFile(filename, c, 0600) } if err != nil { return errors.Wrap(err, "writing file") } return nil } func addExtToFilename(filename, format string) string { if filepath.Ext(filename) == "" { filename = filename + "." + strings.ToLower(format) } return filename } func zeroOutTimestamps(obj interface{}) { zeroOutField(obj, "CreatedAt") zeroOutField(obj, "UpdatedAt") } var zero reflect.Value func zeroOutField(obj interface{}, field string) { ptr := reflect.ValueOf(obj) if ptr.Kind() != reflect.Ptr { return } v := reflect.Indirect(ptr) ts := v.FieldByName(field) if ts == zero { return } ts.Set(reflect.Zero(ts.Type())) } func zeroOutID(obj interface{}, altName *string, withID bool) { // withID is set, export the ID if withID { return } // altName is not set, export the ID if utils.Empty(altName) { return } // zero the ID field zeroOutField(obj, "ID") } deck-1.4.0/file/writer_test.go000066400000000000000000000104051400603563700162470ustar00rootroot00000000000000package file import ( "bytes" "fmt" "io" "os" "sync" "testing" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func captureOutput(f func()) string { reader, writer, err := os.Pipe() if err != nil { panic(err) } stdout := os.Stdout stderr := os.Stderr defer func() { os.Stdout = stdout os.Stderr = stderr }() os.Stdout = writer os.Stderr = writer out := make(chan string) wg := new(sync.WaitGroup) wg.Add(1) go func() { var buf bytes.Buffer wg.Done() io.Copy(&buf, reader) out <- buf.String() }() wg.Wait() f() writer.Close() return <-out } func TestWriteKongStateToStdoutEmptyState(t *testing.T) { var ks, _ = state.NewKongState() var filename = "-" assert := assert.New(t) assert.Equal("-", filename) assert.NotEmpty(t, ks) // YAML output := captureOutput(func() { KongStateToFile(ks, WriteConfig{ Workspace: "foo", Filename: filename, FileFormat: YAML, }) }) assert.Equal("_format_version: \"1.1\"\n_workspace: foo\n", output) // JSON output = captureOutput(func() { KongStateToFile(ks, WriteConfig{ Workspace: "foo", Filename: filename, FileFormat: JSON, }) }) expected := `{ "_format_version": "1.1", "_workspace": "foo" }` assert.Equal(expected, output) } func TestWriteKongStateToStdoutStateWithOneService(t *testing.T) { var ks, _ = state.NewKongState() var filename = "-" assert := assert.New(t) var service state.Service service.ID = kong.String("first") service.Host = kong.String("example.com") service.Name = kong.String("my-service") ks.Services.Add(service) // YAML output := captureOutput(func() { KongStateToFile(ks, WriteConfig{ Filename: filename, FileFormat: YAML, }) }) expected := fmt.Sprintf("_format_version: \"1.1\"\nservices:\n- host: %s\n name: %s\n", *service.Host, *service.Name) assert.Equal(expected, output) // JSON output = captureOutput(func() { KongStateToFile(ks, WriteConfig{ Workspace: "foo", Filename: filename, FileFormat: JSON, }) }) expected = `{ "_format_version": "1.1", "_workspace": "foo", "services": [ { "host": "example.com", "name": "my-service" } ] }` assert.Equal(expected, output) } func TestWriteKongStateToStdoutStateWithOneServiceOneRoute(t *testing.T) { var ks, _ = state.NewKongState() var filename = "-" assert := assert.New(t) var service state.Service service.ID = kong.String("first") service.Host = kong.String("example.com") service.Name = kong.String("my-service") ks.Services.Add(service) var route state.Route route.Name = kong.String("my-route") route.ID = kong.String("first") route.Hosts = kong.StringSlice("example.com", "demo.example.com") route.Service = &kong.Service{ ID: kong.String(*service.ID), Name: kong.String(*service.Name), } ks.Routes.Add(route) // YAML output := captureOutput(func() { KongStateToFile(ks, WriteConfig{ Filename: filename, FileFormat: YAML, }) }) expected := fmt.Sprintf(`_format_version: "1.1" services: - host: %s name: %s routes: - hosts: - %s - %s name: %s `, *service.Host, *service.Name, *route.Hosts[0], *route.Hosts[1], *route.Name) assert.Equal(expected, output) // JSON output = captureOutput(func() { KongStateToFile(ks, WriteConfig{ Workspace: "foo", Filename: filename, FileFormat: JSON, }) }) expected = `{ "_format_version": "1.1", "_workspace": "foo", "services": [ { "host": "example.com", "name": "my-service", "routes": [ { "hosts": [ "example.com", "demo.example.com" ], "name": "my-route" } ] } ] }` assert.Equal(expected, output) } func Test_addExtToFilename(t *testing.T) { type args struct { filename string format string } tests := []struct { name string args args want string }{ { args: args{ filename: "foo", format: "yolo", }, want: "foo.yolo", }, { args: args{ filename: "foo.json", format: "yolo", }, want: "foo.json", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := addExtToFilename(tt.args.filename, tt.args.format); got != tt.want { t.Errorf("addExtToFilename() = %v, want %v", got, tt.want) } }) } } deck-1.4.0/go.mod000066400000000000000000000033371400603563700135420ustar00rootroot00000000000000module github.com/kong/deck require ( github.com/alecthomas/jsonschema v0.0.0-20191017121752-4bb6e3fae4f2 github.com/blang/semver v0.0.0-20190414102917-ba2c2ddd8906 github.com/cenkalti/backoff/v4 v4.1.0 github.com/fatih/color v1.9.0 github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/ghodss/yaml v1.0.0 github.com/hashicorp/go-immutable-radix v1.2.0 // indirect github.com/hashicorp/go-memdb v1.1.2 github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/imdario/mergo v0.3.9 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/kong/go-kong v0.15.0 github.com/mattn/go-colorable v0.1.6 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.2.2 // indirect github.com/onsi/ginkgo v1.7.0 // indirect github.com/onsi/gomega v1.4.3 // indirect github.com/pelletier/go-toml v1.7.0 // indirect github.com/pkg/errors v0.9.1 github.com/sergi/go-diff v1.1.0 // indirect github.com/spf13/afero v1.2.2 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v0.0.7 github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.6.2 github.com/stretchr/testify v1.4.0 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonschema v1.2.0 github.com/yudai/gojsondiff v1.0.0 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect golang.org/x/text v0.3.2 // indirect gopkg.in/ini.v1 v1.55.0 // indirect gopkg.in/yaml.v2 v2.2.8 ) go 1.13 deck-1.4.0/go.sum000066400000000000000000000630761400603563700135750ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 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/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 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/blang/semver v0.0.0-20190414102917-ba2c2ddd8906 h1:KGe2go3VELJLcQfKBUlviUzERqg79dO6VYzCvQxF01w= github.com/blang/semver v0.0.0-20190414102917-ba2c2ddd8906/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc= github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 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/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/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/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.2.0 h1:l6UW37iCXwZkZoAbEYnptSHVE/cQ5bOTPYG5W3vf9+8= github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-memdb v1.1.2 h1:/xeGrWlD1+X26mLdgLDKzQHPsMG8Z3u7N/S1M7/Gjk8= github.com/hashicorp/go-memdb v1.1.2/go.mod h1:LWQ8R70vPrS4OEY9k28D2z8/Zzyu34NVzeRibGAzHO0= 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/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/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/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 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/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kong/go-kong v0.15.0 h1:9Y+7iqh7/0z8/BppAaLEV7ueSSyqK6lJOHFvqJnSSqM= github.com/kong/go-kong v0.15.0/go.mod h1:oF4kdI9l/a8ndDW2ayJA0yhDBpO8Qt2aLiJEv10hqnQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 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/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 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/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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 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 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 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 v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 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.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E= github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 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= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 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/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 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 h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-20181220203305-927f97764cc3/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-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ= gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= deck-1.4.0/kong.yaml000066400000000000000000000112461400603563700142540ustar00rootroot00000000000000_info: select_tags: - managed-by-deck - org-unit-42 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 upstreams: - name: upstream1 algorithm: round-robin targets: - target: 198.51.100.11:80 - target: 198.51.100.12:80 - target: 198.51.100.13:80 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: - cloudops-managed snis: - name: demo1.example.com - name: demo2.example.com - name: demo3.example.com plugins: - name: prometheus enabled: true run_on: first protocols: - http - https consumers: - username: harry keyauth_credentials: - key: iwb6Djkk4HhUlOCmLilDIKh6nZrn90ts hmacauth_credentials: - username: hmac-user secret: yeNZBeqCuk0D3H85VX77Umacf91MwqRo jwt_secrets: - algorithm: HS256 key: MKWeR0nu9OAUR9HrjpUG82Hbfz7ZXsIw secret: 6gkrxTKAraykMSpmnLNEGiEE3Yz8XL6U basicauth_credentials: - username: user1 password: this-password-cant-be-changed acls: - group: foo-group - username: yolo tags: - internal-user plugins: - name: rate-limiting config: day: null fault_tolerant: true hide_client_headers: false hour: null limit_by: consumer minute: 10 month: null policy: redis redis_database: 0 redis_host: redis.common.svc redis_password: null redis_port: 6379 redis_timeout: 2000 second: null year: null enabled: true run_on: first protocols: - http - https deck-1.4.0/main.go000066400000000000000000000007611400603563700137050ustar00rootroot00000000000000package main import ( "fmt" "math/rand" "os" "os/signal" "syscall" "time" "github.com/kong/deck/cmd" ) func registerSignalHandler() { sigs := make(chan os.Signal, 1) done := make(chan struct{}) cmd.SetStopCh(done) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) go func() { sig := <-sigs fmt.Println("received", sig, ", terminating...") close(done) }() } func main() { registerSignalHandler() cmd.Execute() } func init() { rand.Seed(time.Now().UTC().UnixNano()) } deck-1.4.0/print/000077500000000000000000000000001400603563700135625ustar00rootroot00000000000000deck-1.4.0/print/color.go000066400000000000000000000027541400603563700152370ustar00rootroot00000000000000package print import ( "sync" "github.com/fatih/color" ) var ( // mu is used to synchronize writes from multiple goroutines. mu sync.Mutex createPrintf = color.New(color.FgGreen).PrintfFunc() // CreatePrintf is fmt.Printf with red as foreground color. CreatePrintf = func(format string, a ...interface{}) { mu.Lock() defer mu.Unlock() createPrintf(format, a...) } deletePrintf = color.New(color.FgRed).PrintfFunc() // DeletePrintf is fmt.Printf with green as foreground color. DeletePrintf = func(format string, a ...interface{}) { mu.Lock() defer mu.Unlock() deletePrintf(format, a...) } updatePrintf = color.New(color.FgYellow).PrintfFunc() // UpdatePrintf is fmt.Printf with yellow as foreground color. UpdatePrintf = func(format string, a ...interface{}) { mu.Lock() defer mu.Unlock() updatePrintf(format, a...) } createPrintln = color.New(color.FgGreen).PrintlnFunc() // CreatePrintln is fmt.Println with red as foreground color. CreatePrintln = func(a ...interface{}) { mu.Lock() defer mu.Unlock() createPrintln(a...) } deletePrintln = color.New(color.FgRed).PrintlnFunc() // DeletePrintln is fmt.Println with green as foreground color. DeletePrintln = func(a ...interface{}) { mu.Lock() defer mu.Unlock() deletePrintln(a...) } updatePrintln = color.New(color.FgYellow).PrintlnFunc() // UpdatePrintln is fmt.Println with yellow as foreground color. UpdatePrintln = func(a ...interface{}) { mu.Lock() defer mu.Unlock() updatePrintln(a...) } ) deck-1.4.0/reset/000077500000000000000000000000001400603563700135505ustar00rootroot00000000000000deck-1.4.0/reset/reset.go000066400000000000000000000036611400603563700152270ustar00rootroot00000000000000package reset import ( "context" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) // Reset deletes all entities in Kong. func Reset(state *utils.KongRawState, client *kong.Client) error { if state == nil { return errors.New("state cannot be empty") } group, ctx := errgroup.WithContext(context.Background()) group.Go(func() error { // Delete routes before services for _, r := range state.Routes { err := client.Routes.Delete(ctx, r.ID) if err != nil { return err } } return nil }) group.Go(func() error { for _, c := range state.Consumers { err := client.Consumers.Delete(ctx, c.ID) if err != nil { return err } } return nil }) group.Go(func() error { // Upstreams also removes Targets for _, u := range state.Upstreams { err := client.Upstreams.Delete(ctx, u.ID) if err != nil { return err } } return nil }) group.Go(func() error { for _, u := range state.CACertificates { err := client.CACertificates.Delete(nil, u.ID) if err != nil { return err } } return nil }) group.Go(func() error { for _, p := range state.Plugins { // Delete global plugins explicitly since those will not // DELETE ON CASCADE if p.Consumer == nil && p.Service == nil && p.Route == nil { err := client.Plugins.Delete(ctx, p.ID) if err != nil { return err } } } return nil }) err := group.Wait() if err != nil { return err } // Routes must be delted before services can be deleted for _, s := range state.Services { err := client.Services.Delete(nil, s.ID) if err != nil { return err } } // Services must be deleted before certificates can be deleted // Certificates also removes SNIs for _, u := range state.Certificates { err := client.Certificates.Delete(nil, u.ID) if err != nil { return err } } // TODO handle custom entities return nil } deck-1.4.0/scripts/000077500000000000000000000000001400603563700141155ustar00rootroot00000000000000deck-1.4.0/scripts/verify-codegen.sh000077500000000000000000000001521400603563700173600ustar00rootroot00000000000000#!/bin/bash -e cp file/schema.go /tmp/schema.go go generate ./... diff -u /tmp/schema.go file/schema.go deck-1.4.0/solver/000077500000000000000000000000001400603563700137405ustar00rootroot00000000000000deck-1.4.0/solver/aclgroup.go000066400000000000000000000045671400603563700161170ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" ) // aclGroupCRUD implements crud.Actions interface. type aclGroupCRUD struct { client *kong.Client } func aclGroupFromStuct(arg diff.Event) *state.ACLGroup { aclGroup, ok := arg.Obj.(*state.ACLGroup) if !ok { panic("unexpected type, expected *state.Route") } return aclGroup } // Create creates a Route in Kong. // The arg should be of type diff.Event, containing the aclGroup to be created, // else the function will panic. // It returns a the created *state.Route. func (s *aclGroupCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) aclGroup := aclGroupFromStuct(event) cid := "" if !utils.Empty(aclGroup.Consumer.Username) { cid = *aclGroup.Consumer.Username } if !utils.Empty(aclGroup.Consumer.ID) { cid = *aclGroup.Consumer.ID } createdACLGroup, err := s.client.ACLs.Create(nil, &cid, &aclGroup.ACLGroup) if err != nil { return nil, err } return &state.ACLGroup{ACLGroup: *createdACLGroup}, nil } // Delete deletes a Route in Kong. // The arg should be of type diff.Event, containing the aclGroup to be deleted, // else the function will panic. // It returns a the deleted *state.Route. func (s *aclGroupCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) aclGroup := aclGroupFromStuct(event) cid := "" if !utils.Empty(aclGroup.Consumer.Username) { cid = *aclGroup.Consumer.Username } if !utils.Empty(aclGroup.Consumer.ID) { cid = *aclGroup.Consumer.ID } err := s.client.ACLs.Delete(nil, &cid, aclGroup.ID) if err != nil { return nil, err } return aclGroup, nil } // Update updates a Route in Kong. // The arg should be of type diff.Event, containing the aclGroup to be updated, // else the function will panic. // It returns a the updated *state.Route. func (s *aclGroupCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) aclGroup := aclGroupFromStuct(event) cid := "" if !utils.Empty(aclGroup.Consumer.Username) { cid = *aclGroup.Consumer.Username } if !utils.Empty(aclGroup.Consumer.ID) { cid = *aclGroup.Consumer.ID } updatedACLGroup, err := s.client.ACLs.Create(nil, &cid, &aclGroup.ACLGroup) if err != nil { return nil, err } return &state.ACLGroup{ACLGroup: *updatedACLGroup}, nil } deck-1.4.0/solver/basicauth.go000066400000000000000000000046661400603563700162460ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" ) // basicAuthCRUD implements crud.Actions interface. type basicAuthCRUD struct { client *kong.Client } func basicAuthFromStuct(arg diff.Event) *state.BasicAuth { basicAuth, ok := arg.Obj.(*state.BasicAuth) if !ok { panic("unexpected type, expected *state.Route") } return basicAuth } // Create creates a Route in Kong. // The arg should be of type diff.Event, containing the basicAuth to be created, // else the function will panic. // It returns a the created *state.Route. func (s *basicAuthCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) basicAuth := basicAuthFromStuct(event) cid := "" if !utils.Empty(basicAuth.Consumer.Username) { cid = *basicAuth.Consumer.Username } if !utils.Empty(basicAuth.Consumer.ID) { cid = *basicAuth.Consumer.ID } createdBasicAuth, err := s.client.BasicAuths.Create(nil, &cid, &basicAuth.BasicAuth) if err != nil { return nil, err } return &state.BasicAuth{BasicAuth: *createdBasicAuth}, nil } // Delete deletes a Route in Kong. // The arg should be of type diff.Event, containing the basicAuth to be deleted, // else the function will panic. // It returns a the deleted *state.Route. func (s *basicAuthCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) basicAuth := basicAuthFromStuct(event) cid := "" if !utils.Empty(basicAuth.Consumer.Username) { cid = *basicAuth.Consumer.Username } if !utils.Empty(basicAuth.Consumer.ID) { cid = *basicAuth.Consumer.ID } err := s.client.BasicAuths.Delete(nil, &cid, basicAuth.ID) if err != nil { return nil, err } return basicAuth, nil } // Update updates a Route in Kong. // The arg should be of type diff.Event, containing the basicAuth to be updated, // else the function will panic. // It returns a the updated *state.Route. func (s *basicAuthCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) basicAuth := basicAuthFromStuct(event) cid := "" if !utils.Empty(basicAuth.Consumer.Username) { cid = *basicAuth.Consumer.Username } if !utils.Empty(basicAuth.Consumer.ID) { cid = *basicAuth.Consumer.ID } updatedBasicAuth, err := s.client.BasicAuths.Create(nil, &cid, &basicAuth.BasicAuth) if err != nil { return nil, err } return &state.BasicAuth{BasicAuth: *updatedBasicAuth}, nil } deck-1.4.0/solver/ca_cert.go000066400000000000000000000037731400603563700157010ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" ) // caCertificateCRUD implements crud.Actions interface. type caCertificateCRUD struct { client *kong.Client } func caCertFromStuct(arg diff.Event) *state.CACertificate { caCert, ok := arg.Obj.(*state.CACertificate) if !ok { panic("unexpected type, expected *state.CACertificate") } return caCert } // Create creates a CACertificate in Kong. // The arg should be of type diff.Event, containing the certificate to be created, // else the function will panic. // It returns a the created *state.CACertificate. func (s *caCertificateCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) certificate := caCertFromStuct(event) createdCertificate, err := s.client.CACertificates.Create(nil, &certificate.CACertificate) if err != nil { return nil, err } return &state.CACertificate{CACertificate: *createdCertificate}, nil } // Delete deletes a CACertificate in Kong. // The arg should be of type diff.Event, containing the certificate to be deleted, // else the function will panic. // It returns a the deleted *state.CACertificate. func (s *caCertificateCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) certificate := caCertFromStuct(event) err := s.client.CACertificates.Delete(nil, certificate.ID) if err != nil { return nil, err } return certificate, nil } // Update updates a CACertificate in Kong. // The arg should be of type diff.Event, containing the certificate to be updated, // else the function will panic. // It returns a the updated *state.CACertificate. func (s *caCertificateCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) certificate := caCertFromStuct(event) updatedCertificate, err := s.client.CACertificates.Create(nil, &certificate.CACertificate) if err != nil { return nil, err } return &state.CACertificate{CACertificate: *updatedCertificate}, nil } deck-1.4.0/solver/certificate.go000066400000000000000000000037501400603563700165560ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" ) // certificateCRUD implements crud.Actions interface. type certificateCRUD struct { client *kong.Client } func certificateFromStuct(arg diff.Event) *state.Certificate { certificate, ok := arg.Obj.(*state.Certificate) if !ok { panic("unexpected type, expected *state.certificate") } return certificate } // Create creates a Certificate in Kong. // The arg should be of type diff.Event, containing the certificate to be created, // else the function will panic. // It returns a the created *state.Certificate. func (s *certificateCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) certificate := certificateFromStuct(event) createdCertificate, err := s.client.Certificates.Create(nil, &certificate.Certificate) if err != nil { return nil, err } return &state.Certificate{Certificate: *createdCertificate}, nil } // Delete deletes a Certificate in Kong. // The arg should be of type diff.Event, containing the certificate to be deleted, // else the function will panic. // It returns a the deleted *state.Certificate. func (s *certificateCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) certificate := certificateFromStuct(event) err := s.client.Certificates.Delete(nil, certificate.ID) if err != nil { return nil, err } return certificate, nil } // Update updates a Certificate in Kong. // The arg should be of type diff.Event, containing the certificate to be updated, // else the function will panic. // It returns a the updated *state.Certificate. func (s *certificateCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) certificate := certificateFromStuct(event) updatedCertificate, err := s.client.Certificates.Create(nil, &certificate.Certificate) if err != nil { return nil, err } return &state.Certificate{Certificate: *updatedCertificate}, nil } deck-1.4.0/solver/consumer.go000066400000000000000000000035471400603563700161330ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" ) // consumerCRUD implements crud.Actions interface. type consumerCRUD struct { client *kong.Client } func consumerFromStuct(arg diff.Event) *state.Consumer { consumer, ok := arg.Obj.(*state.Consumer) if !ok { panic("unexpected type, expected *state.consumer") } return consumer } // Create creates a Consumer in Kong. // The arg should be of type diff.Event, containing the consumer to be created, // else the function will panic. // It returns a the created *state.Consumer. func (s *consumerCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) consumer := consumerFromStuct(event) createdConsumer, err := s.client.Consumers.Create(nil, &consumer.Consumer) if err != nil { return nil, err } return &state.Consumer{Consumer: *createdConsumer}, nil } // Delete deletes a Consumer in Kong. // The arg should be of type diff.Event, containing the consumer to be deleted, // else the function will panic. // It returns a the deleted *state.Consumer. func (s *consumerCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) consumer := consumerFromStuct(event) err := s.client.Consumers.Delete(nil, consumer.ID) if err != nil { return nil, err } return consumer, nil } // Update updates a Consumer in Kong. // The arg should be of type diff.Event, containing the consumer to be updated, // else the function will panic. // It returns a the updated *state.Consumer. func (s *consumerCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) consumer := consumerFromStuct(event) updatedConsumer, err := s.client.Consumers.Create(nil, &consumer.Consumer) if err != nil { return nil, err } return &state.Consumer{Consumer: *updatedConsumer}, nil } deck-1.4.0/solver/hmacauth.go000066400000000000000000000046061400603563700160670ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" ) // hmacAuthCRUD implements crud.Actions interface. type hmacAuthCRUD struct { client *kong.Client } func hmacAuthFromStuct(arg diff.Event) *state.HMACAuth { hmacAuth, ok := arg.Obj.(*state.HMACAuth) if !ok { panic("unexpected type, expected *state.Route") } return hmacAuth } // Create creates a Route in Kong. // The arg should be of type diff.Event, containing the hmacAuth to be created, // else the function will panic. // It returns a the created *state.Route. func (s *hmacAuthCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) hmacAuth := hmacAuthFromStuct(event) cid := "" if !utils.Empty(hmacAuth.Consumer.Username) { cid = *hmacAuth.Consumer.Username } if !utils.Empty(hmacAuth.Consumer.ID) { cid = *hmacAuth.Consumer.ID } createdHMACAuth, err := s.client.HMACAuths.Create(nil, &cid, &hmacAuth.HMACAuth) if err != nil { return nil, err } return &state.HMACAuth{HMACAuth: *createdHMACAuth}, nil } // Delete deletes a Route in Kong. // The arg should be of type diff.Event, containing the hmacAuth to be deleted, // else the function will panic. // It returns a the deleted *state.Route. func (s *hmacAuthCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) hmacAuth := hmacAuthFromStuct(event) cid := "" if !utils.Empty(hmacAuth.Consumer.Username) { cid = *hmacAuth.Consumer.Username } if !utils.Empty(hmacAuth.Consumer.ID) { cid = *hmacAuth.Consumer.ID } err := s.client.HMACAuths.Delete(nil, &cid, hmacAuth.ID) if err != nil { return nil, err } return hmacAuth, nil } // Update updates a Route in Kong. // The arg should be of type diff.Event, containing the hmacAuth to be updated, // else the function will panic. // It returns a the updated *state.Route. func (s *hmacAuthCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) hmacAuth := hmacAuthFromStuct(event) cid := "" if !utils.Empty(hmacAuth.Consumer.Username) { cid = *hmacAuth.Consumer.Username } if !utils.Empty(hmacAuth.Consumer.ID) { cid = *hmacAuth.Consumer.ID } updatedHMACAuth, err := s.client.HMACAuths.Create(nil, &cid, &hmacAuth.HMACAuth) if err != nil { return nil, err } return &state.HMACAuth{HMACAuth: *updatedHMACAuth}, nil } deck-1.4.0/solver/jwtauth.go000066400000000000000000000045261400603563700157640ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" ) // jwtAuthCRUD implements crud.Actions interface. type jwtAuthCRUD struct { client *kong.Client } func jwtAuthFromStuct(arg diff.Event) *state.JWTAuth { jwtAuth, ok := arg.Obj.(*state.JWTAuth) if !ok { panic("unexpected type, expected *state.Route") } return jwtAuth } // Create creates a Route in Kong. // The arg should be of type diff.Event, containing the jwtAuth to be created, // else the function will panic. // It returns a the created *state.Route. func (s *jwtAuthCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) jwtAuth := jwtAuthFromStuct(event) cid := "" if !utils.Empty(jwtAuth.Consumer.Username) { cid = *jwtAuth.Consumer.Username } if !utils.Empty(jwtAuth.Consumer.ID) { cid = *jwtAuth.Consumer.ID } createdJWTAuth, err := s.client.JWTAuths.Create(nil, &cid, &jwtAuth.JWTAuth) if err != nil { return nil, err } return &state.JWTAuth{JWTAuth: *createdJWTAuth}, nil } // Delete deletes a Route in Kong. // The arg should be of type diff.Event, containing the jwtAuth to be deleted, // else the function will panic. // It returns a the deleted *state.Route. func (s *jwtAuthCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) jwtAuth := jwtAuthFromStuct(event) cid := "" if !utils.Empty(jwtAuth.Consumer.Username) { cid = *jwtAuth.Consumer.Username } if !utils.Empty(jwtAuth.Consumer.ID) { cid = *jwtAuth.Consumer.ID } err := s.client.JWTAuths.Delete(nil, &cid, jwtAuth.ID) if err != nil { return nil, err } return jwtAuth, nil } // Update updates a Route in Kong. // The arg should be of type diff.Event, containing the jwtAuth to be updated, // else the function will panic. // It returns a the updated *state.Route. func (s *jwtAuthCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) jwtAuth := jwtAuthFromStuct(event) cid := "" if !utils.Empty(jwtAuth.Consumer.Username) { cid = *jwtAuth.Consumer.Username } if !utils.Empty(jwtAuth.Consumer.ID) { cid = *jwtAuth.Consumer.ID } updatedJWTAuth, err := s.client.JWTAuths.Create(nil, &cid, &jwtAuth.JWTAuth) if err != nil { return nil, err } return &state.JWTAuth{JWTAuth: *updatedJWTAuth}, nil } deck-1.4.0/solver/keyauth.go000066400000000000000000000040501400603563700157400ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" ) // keyAuthCRUD implements crud.Actions interface. type keyAuthCRUD struct { client *kong.Client } func keyAuthFromStuct(arg diff.Event) *state.KeyAuth { keyAuth, ok := arg.Obj.(*state.KeyAuth) if !ok { panic("unexpected type, expected *state.Route") } return keyAuth } // Create creates a Route in Kong. // The arg should be of type diff.Event, containing the keyAuth to be created, // else the function will panic. // It returns a the created *state.Route. func (s *keyAuthCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) keyAuth := keyAuthFromStuct(event) createdKeyAuth, err := s.client.KeyAuths.Create(nil, keyAuth.Consumer.ID, &keyAuth.KeyAuth) if err != nil { return nil, err } return &state.KeyAuth{KeyAuth: *createdKeyAuth}, nil } // Delete deletes a Route in Kong. // The arg should be of type diff.Event, containing the keyAuth to be deleted, // else the function will panic. // It returns a the deleted *state.Route. func (s *keyAuthCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) keyAuth := keyAuthFromStuct(event) cid := "" if !utils.Empty(keyAuth.Consumer.Username) { cid = *keyAuth.Consumer.Username } if !utils.Empty(keyAuth.Consumer.ID) { cid = *keyAuth.Consumer.ID } err := s.client.KeyAuths.Delete(nil, &cid, keyAuth.ID) if err != nil { return nil, err } return keyAuth, nil } // Update updates a Route in Kong. // The arg should be of type diff.Event, containing the keyAuth to be updated, // else the function will panic. // It returns a the updated *state.Route. func (s *keyAuthCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) keyAuth := keyAuthFromStuct(event) updatedKeyAuth, err := s.client.KeyAuths.Create(nil, keyAuth.Consumer.ID, &keyAuth.KeyAuth) if err != nil { return nil, err } return &state.KeyAuth{KeyAuth: *updatedKeyAuth}, nil } deck-1.4.0/solver/mtlsauth.go000066400000000000000000000042021400603563700161260ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" ) // mtlsAuthCRUD implements crud.Actions interface. type mtlsAuthCRUD struct { client *kong.Client } func mtlsAuthFromStuct(arg diff.Event) *state.MTLSAuth { mtlsAuth, ok := arg.Obj.(*state.MTLSAuth) if !ok { panic("unexpected type, expected *state.Route") } return mtlsAuth } // Create creates an mtls-auth credential in Kong. // The arg should be of type diff.Event, containing the mtlsAuth to be created, // else the function will panic. // It returns a the created *state.Route. func (s *mtlsAuthCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) mtlsAuth := mtlsAuthFromStuct(event) createdMTLSAuth, err := s.client.MTLSAuths.Create(nil, mtlsAuth.Consumer.ID, &mtlsAuth.MTLSAuth) if err != nil { return nil, err } return &state.MTLSAuth{MTLSAuth: *createdMTLSAuth}, nil } // Delete deletes an mtls-auth credential in Kong. // The arg should be of type diff.Event, containing the mtlsAuth to be deleted, // else the function will panic. // It returns a the deleted *state.Route. func (s *mtlsAuthCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) mtlsAuth := mtlsAuthFromStuct(event) cid := "" if !utils.Empty(mtlsAuth.Consumer.Username) { cid = *mtlsAuth.Consumer.Username } if !utils.Empty(mtlsAuth.Consumer.ID) { cid = *mtlsAuth.Consumer.ID } err := s.client.MTLSAuths.Delete(nil, &cid, mtlsAuth.ID) if err != nil { return nil, err } return mtlsAuth, nil } // Update updates an mtls-auth credential in Kong. // The arg should be of type diff.Event, containing the mtlsAuth to be updated, // else the function will panic. // It returns a the updated *state.Route. func (s *mtlsAuthCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) mtlsAuth := mtlsAuthFromStuct(event) updatedMTLSAuth, err := s.client.MTLSAuths.Create(nil, mtlsAuth.Consumer.ID, &mtlsAuth.MTLSAuth) if err != nil { return nil, err } return &state.MTLSAuth{MTLSAuth: *updatedMTLSAuth}, nil } deck-1.4.0/solver/oauth2.go000066400000000000000000000050521400603563700154730ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" ) // oauth2CredCRUD implements crud.Actions interface. type oauth2CredCRUD struct { client *kong.Client } func oauth2CredFromStuct(arg diff.Event) *state.Oauth2Credential { oauth2Cred, ok := arg.Obj.(*state.Oauth2Credential) if !ok { panic("unexpected type, expected *state.Route") } return oauth2Cred } // Create creates a Route in Kong. // The arg should be of type diff.Event, containing the oauth2Cred to be created, // else the function will panic. // It returns a the created *state.Route. func (s *oauth2CredCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) oauth2Cred := oauth2CredFromStuct(event) cid := "" if !utils.Empty(oauth2Cred.Consumer.Username) { cid = *oauth2Cred.Consumer.Username } if !utils.Empty(oauth2Cred.Consumer.ID) { cid = *oauth2Cred.Consumer.ID } createdOauth2Cred, err := s.client.Oauth2Credentials.Create(nil, &cid, &oauth2Cred.Oauth2Credential) if err != nil { return nil, err } return &state.Oauth2Credential{Oauth2Credential: *createdOauth2Cred}, nil } // Delete deletes a Route in Kong. // The arg should be of type diff.Event, containing the oauth2Cred to be deleted, // else the function will panic. // It returns a the deleted *state.Route. func (s *oauth2CredCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) oauth2Cred := oauth2CredFromStuct(event) cid := "" if !utils.Empty(oauth2Cred.Consumer.Username) { cid = *oauth2Cred.Consumer.Username } if !utils.Empty(oauth2Cred.Consumer.ID) { cid = *oauth2Cred.Consumer.ID } err := s.client.Oauth2Credentials.Delete(nil, &cid, oauth2Cred.ID) if err != nil { return nil, err } return oauth2Cred, nil } // Update updates a Route in Kong. // The arg should be of type diff.Event, containing the oauth2Cred to be updated, // else the function will panic. // It returns a the updated *state.Route. func (s *oauth2CredCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) oauth2Cred := oauth2CredFromStuct(event) cid := "" if !utils.Empty(oauth2Cred.Consumer.Username) { cid = *oauth2Cred.Consumer.Username } if !utils.Empty(oauth2Cred.Consumer.ID) { cid = *oauth2Cred.Consumer.ID } updatedOauth2Cred, err := s.client.Oauth2Credentials.Create(nil, &cid, &oauth2Cred.Oauth2Credential) if err != nil { return nil, err } return &state.Oauth2Credential{Oauth2Credential: *updatedOauth2Cred}, nil } deck-1.4.0/solver/plugin.go000066400000000000000000000034231400603563700155670ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" ) // pluginCRUD implements crud.Actions interface. type pluginCRUD struct { client *kong.Client } func pluginFromStuct(arg diff.Event) *state.Plugin { plugin, ok := arg.Obj.(*state.Plugin) if !ok { panic("unexpected type, expected *state.Plugin") } return plugin } // Create creates a Plugin in Kong. // The arg should be of type diff.Event, containing the plugin to be created, // else the function will panic. // It returns a the created *state.Plugin. func (s *pluginCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) plugin := pluginFromStuct(event) createdPlugin, err := s.client.Plugins.Create(nil, &plugin.Plugin) if err != nil { return nil, err } return &state.Plugin{Plugin: *createdPlugin}, nil } // Delete deletes a Plugin in Kong. // The arg should be of type diff.Event, containing the plugin to be deleted, // else the function will panic. // It returns a the deleted *state.Plugin. func (s *pluginCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) plugin := pluginFromStuct(event) err := s.client.Plugins.Delete(nil, plugin.ID) if err != nil { return nil, err } return plugin, nil } // Update updates a Plugin in Kong. // The arg should be of type diff.Event, containing the plugin to be updated, // else the function will panic. // It returns a the updated *state.Plugin. func (s *pluginCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) plugin := pluginFromStuct(event) updatedPlugin, err := s.client.Plugins.Create(nil, &plugin.Plugin) if err != nil { return nil, err } return &state.Plugin{Plugin: *updatedPlugin}, nil } deck-1.4.0/solver/route.go000066400000000000000000000033471400603563700154340ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" ) // routeCRUD implements crud.Actions interface. type routeCRUD struct { client *kong.Client } func routeFromStuct(arg diff.Event) *state.Route { route, ok := arg.Obj.(*state.Route) if !ok { panic("unexpected type, expected *state.Route") } return route } // Create creates a Route in Kong. // The arg should be of type diff.Event, containing the route to be created, // else the function will panic. // It returns a the created *state.Route. func (s *routeCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) route := routeFromStuct(event) createdRoute, err := s.client.Routes.Create(nil, &route.Route) if err != nil { return nil, err } return &state.Route{Route: *createdRoute}, nil } // Delete deletes a Route in Kong. // The arg should be of type diff.Event, containing the route to be deleted, // else the function will panic. // It returns a the deleted *state.Route. func (s *routeCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) route := routeFromStuct(event) err := s.client.Routes.Delete(nil, route.ID) if err != nil { return nil, err } return route, nil } // Update updates a Route in Kong. // The arg should be of type diff.Event, containing the route to be updated, // else the function will panic. // It returns a the updated *state.Route. func (s *routeCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) route := routeFromStuct(event) updatedRoute, err := s.client.Routes.Create(nil, &route.Route) if err != nil { return nil, err } return &state.Route{Route: *updatedRoute}, nil } deck-1.4.0/solver/service.go000066400000000000000000000034741400603563700157370ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" ) // serviceCRUD implements crud.Actions interface. type serviceCRUD struct { client *kong.Client } func serviceFromStuct(arg diff.Event) *state.Service { service, ok := arg.Obj.(*state.Service) if !ok { panic("unexpected type, expected *state.service") } return service } // Create creates a Service in Kong. // The arg should be of type diff.Event, containing the service to be created, // else the function will panic. // It returns a the created *state.Service. func (s *serviceCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) service := serviceFromStuct(event) createdService, err := s.client.Services.Create(nil, &service.Service) if err != nil { return nil, err } return &state.Service{Service: *createdService}, nil } // Delete deletes a Service in Kong. // The arg should be of type diff.Event, containing the service to be deleted, // else the function will panic. // It returns a the deleted *state.Service. func (s *serviceCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) service := serviceFromStuct(event) err := s.client.Services.Delete(nil, service.ID) if err != nil { return nil, err } return service, nil } // Update updates a Service in Kong. // The arg should be of type diff.Event, containing the service to be updated, // else the function will panic. // It returns a the updated *state.Service. func (s *serviceCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) service := serviceFromStuct(event) updatedService, err := s.client.Services.Create(nil, &service.Service) if err != nil { return nil, err } return &state.Service{Service: *updatedService}, nil } deck-1.4.0/solver/sni.go000066400000000000000000000032211400603563700150560ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" ) // sniCRUD implements crud.Actions interface. type sniCRUD struct { client *kong.Client } func sniFromStuct(arg diff.Event) *state.SNI { sni, ok := arg.Obj.(*state.SNI) if !ok { panic("unexpected type, expected *state.SNI") } return sni } // Create creates a SNI in Kong. // The arg should be of type diff.Event, containing the sni to be created, // else the function will panic. // It returns a the created *state.SNI. func (s *sniCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) sni := sniFromStuct(event) createdSNI, err := s.client.SNIs.Create(nil, &sni.SNI) if err != nil { return nil, err } return &state.SNI{SNI: *createdSNI}, nil } // Delete deletes a SNI in Kong. // The arg should be of type diff.Event, containing the sni to be deleted, // else the function will panic. // It returns a the deleted *state.SNI. func (s *sniCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) sni := sniFromStuct(event) err := s.client.SNIs.Delete(nil, sni.ID) if err != nil { return nil, err } return sni, nil } // Update updates a SNI in Kong. // The arg should be of type diff.Event, containing the sni to be updated, // else the function will panic. // It returns a the updated *state.SNI. func (s *sniCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) sni := sniFromStuct(event) updatedSNI, err := s.client.SNIs.Create(nil, &sni.SNI) if err != nil { return nil, err } return &state.SNI{SNI: *updatedSNI}, nil } deck-1.4.0/solver/solver.go000066400000000000000000000053221400603563700156030ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/print" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" "github.com/pkg/errors" ) // Stats holds the stats related to a Solve. type Stats struct { CreateOps int UpdateOps int DeleteOps int } // Solve generates a diff and walks the graph. func Solve(doneCh chan struct{}, syncer *diff.Syncer, client *kong.Client, parallelism int, dry bool) (Stats, []error) { r := buildRegistry(client) var stats Stats recordOp := func(op crud.Op) { switch op { case crud.Create: stats.CreateOps = stats.CreateOps + 1 case crud.Update: stats.UpdateOps = stats.UpdateOps + 1 case crud.Delete: stats.DeleteOps = stats.DeleteOps + 1 } } errs := syncer.Run(doneCh, parallelism, func(e diff.Event) (crud.Arg, error) { var err error var result crud.Arg c := e.Obj.(state.ConsoleString) switch e.Op { case crud.Create: print.CreatePrintln("creating", e.Kind, c.Console()) case crud.Update: diffString, err := getDiff(e.OldObj, e.Obj) if err != nil { return nil, err } print.UpdatePrintln("updating", e.Kind, c.Console(), diffString) case crud.Delete: print.DeletePrintln("deleting", e.Kind, c.Console()) default: panic("unknown operation " + e.Op.String()) } if !dry { // sync mode // fire the request to Kong result, err = r.Do(e.Kind, e.Op, e) if err != nil { return nil, errors.Wrapf(err, "%v %v %v failed", e.Op, e.Kind, c.Console()) } } else { // diff mode // return the new obj as is result = e.Obj } // record operation in both: diff and sync commands recordOp(e.Op) return result, nil }) return stats, errs } func buildRegistry(client *kong.Client) *crud.Registry { var r crud.Registry r.MustRegister("service", &serviceCRUD{client: client}) r.MustRegister("route", &routeCRUD{client: client}) r.MustRegister("upstream", &upstreamCRUD{client: client}) r.MustRegister("target", &targetCRUD{client: client}) r.MustRegister("certificate", &certificateCRUD{client: client}) r.MustRegister("sni", &sniCRUD{client: client}) r.MustRegister("ca_certificate", &caCertificateCRUD{client: client}) r.MustRegister("plugin", &pluginCRUD{client: client}) r.MustRegister("consumer", &consumerCRUD{client: client}) r.MustRegister("key-auth", &keyAuthCRUD{client: client}) r.MustRegister("hmac-auth", &hmacAuthCRUD{client: client}) r.MustRegister("jwt-auth", &jwtAuthCRUD{client: client}) r.MustRegister("basic-auth", &basicAuthCRUD{client: client}) r.MustRegister("acl-group", &aclGroupCRUD{client: client}) r.MustRegister("oauth2-cred", &oauth2CredCRUD{client: client}) r.MustRegister("mtls-auth", &mtlsAuthCRUD{client: client}) return &r } deck-1.4.0/solver/target.go000066400000000000000000000037621400603563700155650ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" ) // targetCRUD implements crud.Actions interface. type targetCRUD struct { client *kong.Client } func targetFromStuct(arg diff.Event) *state.Target { target, ok := arg.Obj.(*state.Target) if !ok { panic("unexpected type, expected *state.Target") } return target } // Create creates a Target in Kong. // The arg should be of type diff.Event, containing the target to be created, // else the function will panic. // It returns a the created *state.Target. func (s *targetCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) target := targetFromStuct(event) createdTarget, err := s.client.Targets.Create(nil, target.Upstream.ID, &target.Target) if err != nil { return nil, err } return &state.Target{Target: *createdTarget}, nil } // Delete deletes a Target in Kong. // The arg should be of type diff.Event, containing the target to be deleted, // else the function will panic. // It returns a the deleted *state.Target. func (s *targetCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) target := targetFromStuct(event) err := s.client.Targets.Delete(nil, target.Upstream.ID, target.ID) if err != nil { return nil, err } return target, nil } // Update updates a Target in Kong. // The arg should be of type diff.Event, containing the target to be updated, // else the function will panic. // It returns a the updated *state.Target. func (s *targetCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) target := targetFromStuct(event) // Targets in Kong cannot be updated err := s.client.Targets.Delete(nil, target.Upstream.ID, target.ID) if err != nil { return nil, err } target.ID = nil createdTarget, err := s.client.Targets.Create(nil, target.Upstream.ID, &target.Target) if err != nil { return nil, err } return &state.Target{Target: *createdTarget}, nil } deck-1.4.0/solver/upstream.go000066400000000000000000000035471400603563700161400ustar00rootroot00000000000000package solver import ( "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/kong/deck/state" "github.com/kong/go-kong/kong" ) // upstreamCRUD implements crud.Actions interface. type upstreamCRUD struct { client *kong.Client } func upstreamFromStuct(arg diff.Event) *state.Upstream { upstream, ok := arg.Obj.(*state.Upstream) if !ok { panic("unexpected type, expected *state.upstream") } return upstream } // Create creates a Upstream in Kong. // The arg should be of type diff.Event, containing the upstream to be created, // else the function will panic. // It returns a the created *state.Upstream. func (s *upstreamCRUD) Create(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) upstream := upstreamFromStuct(event) createdUpstream, err := s.client.Upstreams.Create(nil, &upstream.Upstream) if err != nil { return nil, err } return &state.Upstream{Upstream: *createdUpstream}, nil } // Delete deletes a Upstream in Kong. // The arg should be of type diff.Event, containing the upstream to be deleted, // else the function will panic. // It returns a the deleted *state.Upstream. func (s *upstreamCRUD) Delete(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) upstream := upstreamFromStuct(event) err := s.client.Upstreams.Delete(nil, upstream.ID) if err != nil { return nil, err } return upstream, nil } // Update updates a Upstream in Kong. // The arg should be of type diff.Event, containing the upstream to be updated, // else the function will panic. // It returns a the updated *state.Upstream. func (s *upstreamCRUD) Update(arg ...crud.Arg) (crud.Arg, error) { event := eventFromArg(arg[0]) upstream := upstreamFromStuct(event) updatedUpstream, err := s.client.Upstreams.Create(nil, &upstream.Upstream) if err != nil { return nil, err } return &state.Upstream{Upstream: *updatedUpstream}, nil } deck-1.4.0/solver/utils.go000066400000000000000000000016071400603563700154330ustar00rootroot00000000000000package solver import ( "encoding/json" "github.com/kong/deck/crud" "github.com/kong/deck/diff" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" ) var ( differ = gojsondiff.New() ) func getDiff(a, b interface{}) (string, error) { aJSON, err := json.Marshal(a) if err != nil { return "", err } bJSON, err := json.Marshal(b) if err != nil { return "", err } d, err := differ.Compare(aJSON, bJSON) if err != nil { return "", err } var leftObject map[string]interface{} err = json.Unmarshal(aJSON, &leftObject) if err != nil { return "", err } formatter := formatter.NewAsciiFormatter(leftObject, formatter.AsciiFormatterConfig{}) diffString, err := formatter.Format(d) return diffString, err } func eventFromArg(arg crud.Arg) diff.Event { event, ok := arg.(diff.Event) if !ok { panic("unexpected type, expected diff.Event") } return event } deck-1.4.0/state/000077500000000000000000000000001400603563700135465ustar00rootroot00000000000000deck-1.4.0/state/aclgroup.go000066400000000000000000000130411400603563700157100ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/state/indexers" "github.com/kong/deck/utils" "github.com/pkg/errors" ) var ( errGroupRequired = errors.New("name of ACL group required") errConsumerRequired = errors.New("consumer required") ) const ( aclGroupTableName = "aclGroup" aclGroupsByConsumerID = "aclGroupsByConsumerID" ) var aclGroupTableSchema = &memdb.TableSchema{ Name: aclGroupTableName, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, "group": { Name: "group", Indexer: &memdb.StringFieldIndex{Field: "Group"}, }, all: allIndex, // foreign aclGroupsByConsumerID: { Name: aclGroupsByConsumerID, Indexer: &indexers.SubFieldIndexer{ Fields: []indexers.Field{ { Struct: "Consumer", Sub: "ID", }, }, }, }, }, } // ACLGroupsCollection stores and indexes acl-group credentials. type ACLGroupsCollection collection // Add adds aclGroup to ACLGroupsCollection func (k *ACLGroupsCollection) Add(aclGroup ACLGroup) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(aclGroup.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := insertACLGroup(txn, aclGroup) if err != nil { return err } txn.Commit() return nil } func insertACLGroup(txn *memdb.Txn, aclGroup ACLGroup) error { if utils.Empty(aclGroup.ID) { return errIDRequired } // err out if group with same ID is present _, err := getACLGroupByID(txn, *aclGroup.ID) if err == nil { return fmt.Errorf("inserting acl-group %v: %w", aclGroup.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } // check if the same combination is present if utils.Empty(aclGroup.Group) { return errGroupRequired } if aclGroup.Consumer == nil || utils.Empty(aclGroup.Consumer.ID) { return errConsumerRequired } _, err = getACLGroup(txn, *aclGroup.Consumer.ID, *aclGroup.Group) if err == nil { return fmt.Errorf("inserting acl-group %v: %w", aclGroup.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } // all good err = txn.Insert(aclGroupTableName, &aclGroup) if err != nil { return err } return nil } func getACLGroupByID(txn *memdb.Txn, id string) (*ACLGroup, error) { res, err := multiIndexLookupUsingTxn(txn, aclGroupTableName, []string{"id"}, id) if err != nil { return nil, err } aclGroup, ok := res.(*ACLGroup) if !ok { panic(unexpectedType) } return &ACLGroup{ACLGroup: *aclGroup.DeepCopy()}, nil } // GetByID gets an acl-group with id. func (k *ACLGroupsCollection) GetByID(id string) (*ACLGroup, error) { if id == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() return getACLGroupByID(txn, id) } func getACLGroup(txn *memdb.Txn, consumerID, groupOrID string) (*ACLGroup, error) { groups, err := getAllACLGroupsByConsumerID(txn, consumerID) if err != nil { return nil, err } for _, group := range groups { if groupOrID == *group.ID || groupOrID == *group.Group { return &ACLGroup{ACLGroup: *group.DeepCopy()}, nil } } return nil, ErrNotFound } func getAllACLGroupsByConsumerID(txn *memdb.Txn, consumerID string) ([]*ACLGroup, error) { iter, err := txn.Get(aclGroupTableName, aclGroupsByConsumerID, consumerID) if err != nil { return nil, err } var res []*ACLGroup for el := iter.Next(); el != nil; el = iter.Next() { r, ok := el.(*ACLGroup) if !ok { panic(unexpectedType) } res = append(res, &ACLGroup{ACLGroup: *r.DeepCopy()}) } return res, nil } // Get gets a acl-group for a consumer by group or ID. func (k *ACLGroupsCollection) Get(consumerID, groupOrID string) (*ACLGroup, error) { if groupOrID == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() return getACLGroup(txn, consumerID, groupOrID) } // GetAllByConsumerID returns all acl-group credentials // belong to a Consumer with id. func (k *ACLGroupsCollection) GetAllByConsumerID(id string) ([]*ACLGroup, error) { if id == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() return getAllACLGroupsByConsumerID(txn, id) } // Update updates an existing acl-group credential. func (k *ACLGroupsCollection) Update(aclGroup ACLGroup) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(aclGroup.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteACLGroup(txn, *aclGroup.ID) if err != nil { return err } err = insertACLGroup(txn, aclGroup) if err != nil { return err } txn.Commit() return nil } func deleteACLGroup(txn *memdb.Txn, id string) error { group, err := getACLGroupByID(txn, id) if err != nil { return err } err = txn.Delete(aclGroupTableName, group) if err != nil { return err } return nil } // Delete deletes an acl-group by id. func (k *ACLGroupsCollection) Delete(id string) error { if id == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteACLGroup(txn, id) if err != nil { return err } txn.Commit() return nil } // GetAll gets all acl-groups. func (k *ACLGroupsCollection) GetAll() ([]*ACLGroup, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(aclGroupTableName, all, true) if err != nil { return nil, err } var res []*ACLGroup for el := iter.Next(); el != nil; el = iter.Next() { r, ok := el.(*ACLGroup) if !ok { panic(unexpectedType) } res = append(res, &ACLGroup{ACLGroup: *r.DeepCopy()}) } txn.Commit() return res, nil } deck-1.4.0/state/aclgroup_test.go000066400000000000000000000122421400603563700167510ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func aclGroupsCollection() *ACLGroupsCollection { return state().ACLGroups } func TestACLGroupInsert(t *testing.T) { assert := assert.New(t) collection := aclGroupsCollection() var aclGroup ACLGroup assert.NotNil(collection.Add(aclGroup)) aclGroup.Group = kong.String("my-group") aclGroup.ID = kong.String("first") err := collection.Add(aclGroup) assert.NotNil(err) var aclGroup2 ACLGroup aclGroup2.Group = kong.String("my-group") aclGroup2.ID = kong.String("first") aclGroup2.Consumer = &kong.Consumer{ ID: kong.String("consumer-id"), } err = collection.Add(aclGroup2) assert.Nil(err) // re-insert err = collection.Add(aclGroup2) assert.NotNil(err) // re-insert with a different ID aclGroup2.ID = kong.String("second") err = collection.Add(aclGroup2) assert.NotNil(err) // re-insert for different consumer aclGroup2.Consumer = &kong.Consumer{ ID: kong.String("consumer2-id"), } err = collection.Add(aclGroup2) assert.Nil(err) } func TestACLGroupGetByID(t *testing.T) { assert := assert.New(t) collection := aclGroupsCollection() var aclGroup ACLGroup aclGroup.Group = kong.String("my-group") aclGroup.ID = kong.String("first") aclGroup.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), } err := collection.Add(aclGroup) assert.Nil(err) res, err := collection.GetByID("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-group", *res.Group) res, err = collection.GetByID("my-group") assert.NotNil(err) assert.Nil(res) res, err = collection.GetByID("does-not-exist") assert.NotNil(err) assert.Nil(res) } func TestACLGroupGet(t *testing.T) { assert := assert.New(t) collection := aclGroupsCollection() populateWithACLGroupFixtures(assert, collection) res, err := collection.Get("first", "does-not-exist") assert.NotNil(err) assert.Nil(res) res, err = collection.Get("does-not-exist", "my-group12") assert.NotNil(err) assert.Nil(res) res, err = collection.Get("consumer1-id", "my-group12") assert.Nil(err) assert.NotNil(res) } func TestACLGroupUpdate(t *testing.T) { assert := assert.New(t) collection := aclGroupsCollection() var aclGroup ACLGroup aclGroup.Group = kong.String("my-group") aclGroup.ID = kong.String("first") aclGroup.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), } err := collection.Add(aclGroup) assert.Nil(err) res, err := collection.Get("consumer1-id", "first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-group", *res.Group) res.Group = kong.String("my-group2") err = collection.Update(*res) assert.Nil(err) res, err = collection.Get("consumer1-id", "my-group") assert.NotNil(err) assert.Nil(res) res, err = collection.Get("consumer1-id", "my-group2") assert.Nil(err) assert.Equal("first", *res.ID) } func TestACLGroupDelete(t *testing.T) { assert := assert.New(t) collection := aclGroupsCollection() var aclGroup ACLGroup aclGroup.Group = kong.String("my-group1") aclGroup.ID = kong.String("first") aclGroup.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), } err := collection.Add(aclGroup) assert.Nil(err) res, err := collection.Get("consumer1-id", "my-group1") assert.Nil(err) assert.NotNil(res) err = collection.Delete(*res.ID) assert.Nil(err) res, err = collection.Get("consumer1-id", "my-group1") assert.NotNil(err) assert.Nil(res) // delete a non-existing one err = collection.Delete("first") assert.NotNil(err) err = collection.Delete("my-group1") assert.NotNil(err) } func TestACLGroupGetAll(t *testing.T) { assert := assert.New(t) collection := aclGroupsCollection() populateWithACLGroupFixtures(assert, collection) aclGroups, err := collection.GetAll() assert.Nil(err) assert.Equal(5, len(aclGroups)) } func TestACLGroupGetByConsumer(t *testing.T) { assert := assert.New(t) collection := aclGroupsCollection() populateWithACLGroupFixtures(assert, collection) aclGroups, err := collection.GetAllByConsumerID("consumer1-id") assert.Nil(err) assert.Equal(3, len(aclGroups)) } func populateWithACLGroupFixtures(assert *assert.Assertions, collection *ACLGroupsCollection) { aclGroups := []ACLGroup{ { ACLGroup: kong.ACLGroup{ Group: kong.String("my-group11"), ID: kong.String("first"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), }, }, }, { ACLGroup: kong.ACLGroup{ Group: kong.String("my-group12"), ID: kong.String("second"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), }, }, }, { ACLGroup: kong.ACLGroup{ Group: kong.String("my-group13"), ID: kong.String("third"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), }, }, }, { ACLGroup: kong.ACLGroup{ Group: kong.String("my-group21"), ID: kong.String("fourth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), }, }, }, { ACLGroup: kong.ACLGroup{ Group: kong.String("my-group22"), ID: kong.String("fifth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), }, }, }, } for _, k := range aclGroups { err := collection.Add(k) assert.Nil(err) } } deck-1.4.0/state/basicauth.go000066400000000000000000000041401400603563700160370ustar00rootroot00000000000000package state // BasicAuthsCollection stores and indexes basic-auth credentials. type BasicAuthsCollection struct { credentialsCollection } func newBasicAuthsCollection(common collection) *BasicAuthsCollection { return &BasicAuthsCollection{ credentialsCollection: credentialsCollection{ collection: common, CredType: "basic-auth", }, } } // Add adds a basic-auth credential to BasicAuthsCollection func (k *BasicAuthsCollection) Add(basicAuth BasicAuth) error { cred := (entity)(&basicAuth) return k.credentialsCollection.Add(cred) } // Get gets a basic-auth credential by key or ID. func (k *BasicAuthsCollection) Get(keyOrID string) (*BasicAuth, error) { cred, err := k.credentialsCollection.Get(keyOrID) if err != nil { return nil, err } basicAuth, ok := cred.(*BasicAuth) if !ok { panic(unexpectedType) } return &BasicAuth{BasicAuth: *basicAuth.DeepCopy()}, nil } // GetAllByConsumerID returns all basic-auth credentials // belong to a Consumer with id. func (k *BasicAuthsCollection) GetAllByConsumerID(id string) ([]*BasicAuth, error) { creds, err := k.credentialsCollection.GetAllByConsumerID(id) if err != nil { return nil, err } var res []*BasicAuth for _, cred := range creds { r, ok := cred.(*BasicAuth) if !ok { panic(unexpectedType) } res = append(res, &BasicAuth{BasicAuth: *r.DeepCopy()}) } return res, nil } // Update updates an existing basic-auth credential. func (k *BasicAuthsCollection) Update(basicAuth BasicAuth) error { cred := (entity)(&basicAuth) return k.credentialsCollection.Update(cred) } // Delete deletes a basic-auth credential by key or ID. func (k *BasicAuthsCollection) Delete(keyOrID string) error { return k.credentialsCollection.Delete(keyOrID) } // GetAll gets all basic-auth credentials. func (k *BasicAuthsCollection) GetAll() ([]*BasicAuth, error) { creds, err := k.credentialsCollection.GetAll() if err != nil { return nil, err } var res []*BasicAuth for _, cred := range creds { r, ok := cred.(*BasicAuth) if !ok { panic(unexpectedType) } res = append(res, &BasicAuth{BasicAuth: *r.DeepCopy()}) } return res, nil } deck-1.4.0/state/basicauth_test.go000066400000000000000000000121101400603563700170720ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func basicAuthsCollection() *BasicAuthsCollection { return state().BasicAuths } func TestBasicAuthInsert(t *testing.T) { assert := assert.New(t) collection := basicAuthsCollection() var basicAuth BasicAuth basicAuth.ID = kong.String("first") err := collection.Add(basicAuth) assert.NotNil(err) basicAuth.Username = kong.String("my-username") err = collection.Add(basicAuth) assert.NotNil(err) var basicAuth2 BasicAuth basicAuth2.Username = kong.String("my-username") basicAuth2.ID = kong.String("first") basicAuth2.Consumer = &kong.Consumer{ ID: kong.String("consumer-id"), Username: kong.String("my-username"), } err = collection.Add(basicAuth2) assert.Nil(err) } func TestBasicAuthGet(t *testing.T) { assert := assert.New(t) collection := basicAuthsCollection() var basicAuth BasicAuth basicAuth.Username = kong.String("my-username") basicAuth.ID = kong.String("first") basicAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(basicAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-username", *res.Username) res, err = collection.Get("my-username") assert.Nil(err) assert.NotNil(res) assert.Equal("first", *res.ID) assert.Equal("consumer1-id", *res.Consumer.ID) res, err = collection.Get("does-not-exist") assert.NotNil(err) assert.Nil(res) } func TestBasicAuthUpdate(t *testing.T) { assert := assert.New(t) collection := basicAuthsCollection() var basicAuth BasicAuth basicAuth.Username = kong.String("my-username") basicAuth.ID = kong.String("first") basicAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(basicAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-username", *res.Username) res.Username = kong.String("my-username2") res.Password = kong.String("password") err = collection.Update(*res) assert.Nil(err) res, err = collection.Get("my-username") assert.NotNil(err) assert.Nil(res) res, err = collection.Get("my-username2") assert.Nil(err) assert.Equal("first", *res.ID) assert.Equal("password", *res.Password) } func TestBasicAuthDelete(t *testing.T) { assert := assert.New(t) collection := basicAuthsCollection() var basicAuth BasicAuth basicAuth.Username = kong.String("my-username1") basicAuth.ID = kong.String("first") basicAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(basicAuth) assert.Nil(err) res, err := collection.Get("my-username1") assert.Nil(err) assert.NotNil(res) err = collection.Delete(*res.ID) assert.Nil(err) res, err = collection.Get("my-username1") assert.NotNil(err) assert.Nil(res) // delete a non-existing one err = collection.Delete("first") assert.NotNil(err) err = collection.Delete("my-username1") assert.NotNil(err) } func TestBasicAuthGetAll(t *testing.T) { assert := assert.New(t) collection := basicAuthsCollection() populateWithBasicAuthFixtures(assert, collection) basicAuths, err := collection.GetAll() assert.Nil(err) assert.Equal(5, len(basicAuths)) } func TestBasicAuthGetByConsumer(t *testing.T) { assert := assert.New(t) collection := basicAuthsCollection() populateWithBasicAuthFixtures(assert, collection) basicAuths, err := collection.GetAllByConsumerID("consumer1-id") assert.Nil(err) assert.Equal(3, len(basicAuths)) } func populateWithBasicAuthFixtures(assert *assert.Assertions, collection *BasicAuthsCollection) { basicAuths := []BasicAuth{ { BasicAuth: kong.BasicAuth{ Username: kong.String("my-username11"), ID: kong.String("first"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { BasicAuth: kong.BasicAuth{ Username: kong.String("my-username12"), ID: kong.String("second"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { BasicAuth: kong.BasicAuth{ Username: kong.String("my-username13"), ID: kong.String("third"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { BasicAuth: kong.BasicAuth{ Username: kong.String("my-username21"), ID: kong.String("fourth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), Username: kong.String("consumer2-name"), }, }, }, { BasicAuth: kong.BasicAuth{ Username: kong.String("my-username22"), ID: kong.String("fifth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), Username: kong.String("consumer2-name"), }, }, }, } for _, k := range basicAuths { err := collection.Add(k) assert.Nil(err) } } deck-1.4.0/state/builder.go000066400000000000000000000104511400603563700155240ustar00rootroot00000000000000package state import ( "github.com/kong/deck/utils" "github.com/pkg/errors" ) // Get builds a KongState from a raw representation of Kong. func Get(raw *utils.KongRawState) (*KongState, error) { kongState, err := NewKongState() if err != nil { return nil, errors.Wrap(err, "creating new in-memory state of Kong") } for _, s := range raw.Services { err := kongState.Services.Add(Service{Service: *s}) if err != nil { return nil, errors.Wrap(err, "inserting service into state") } } for _, r := range raw.Routes { err = kongState.Routes.Add(Route{Route: *r}) if err != nil { return nil, errors.Wrap(err, "inserting route into state") } } for _, c := range raw.Consumers { err := kongState.Consumers.Add(Consumer{Consumer: *c}) if err != nil { return nil, errors.Wrap(err, "inserting consumer into state") } } ensureConsumer := func(consumerID string) (bool, error) { _, err := kongState.Consumers.Get(consumerID) if err != nil { if err == ErrNotFound { return false, nil } return false, errors.Wrapf(err, "looking up consumer '%v'", consumerID) } return true, nil } for _, cred := range raw.KeyAuths { ok, err := ensureConsumer(*cred.Consumer.ID) if err != nil { return nil, err } if !ok { continue } err = kongState.KeyAuths.Add(KeyAuth{KeyAuth: *cred}) if err != nil { return nil, errors.Wrap(err, "inserting key-auth into state") } } for _, cred := range raw.HMACAuths { ok, err := ensureConsumer(*cred.Consumer.ID) if err != nil { return nil, err } if !ok { continue } err = kongState.HMACAuths.Add(HMACAuth{HMACAuth: *cred}) if err != nil { return nil, errors.Wrap(err, "inserting hmac-auth into state") } } for _, cred := range raw.JWTAuths { ok, err := ensureConsumer(*cred.Consumer.ID) if err != nil { return nil, err } if !ok { continue } err = kongState.JWTAuths.Add(JWTAuth{JWTAuth: *cred}) if err != nil { return nil, errors.Wrap(err, "inserting jwt into state") } } for _, cred := range raw.BasicAuths { ok, err := ensureConsumer(*cred.Consumer.ID) if err != nil { return nil, err } if !ok { continue } err = kongState.BasicAuths.Add(BasicAuth{BasicAuth: *cred}) if err != nil { return nil, errors.Wrap(err, "inserting basic-auth into state") } } for _, cred := range raw.Oauth2Creds { ok, err := ensureConsumer(*cred.Consumer.ID) if err != nil { return nil, err } if !ok { continue } err = kongState.Oauth2Creds.Add(Oauth2Credential{Oauth2Credential: *cred}) if err != nil { return nil, errors.Wrap(err, "inserting oauth2-cred into state") } } for _, cred := range raw.ACLGroups { ok, err := ensureConsumer(*cred.Consumer.ID) if err != nil { return nil, err } if !ok { continue } err = kongState.ACLGroups.Add(ACLGroup{ACLGroup: *cred}) if err != nil { return nil, errors.Wrap(err, "inserting basic-auth into state") } } for _, cred := range raw.MTLSAuths { ok, err := ensureConsumer(*cred.Consumer.ID) if err != nil { return nil, err } if !ok { continue } err = kongState.MTLSAuths.Add(MTLSAuth{MTLSAuth: *cred}) if err != nil { return nil, errors.Wrap(err, "inserting mtls-auth into state") } } for _, u := range raw.Upstreams { err := kongState.Upstreams.Add(Upstream{Upstream: *u}) if err != nil { return nil, errors.Wrap(err, "inserting upstream into state") } } for _, t := range raw.Targets { err = kongState.Targets.Add(Target{Target: *t}) if err != nil { return nil, errors.Wrap(err, "inserting target into state") } } for _, c := range raw.Certificates { err := kongState.Certificates.Add(Certificate{Certificate: *c}) if err != nil { return nil, errors.Wrap(err, "inserting certificate into state") } } for _, s := range raw.SNIs { err := kongState.SNIs.Add(SNI{SNI: *s}) if err != nil { return nil, errors.Wrap(err, "inserting sni into state") } } for _, c := range raw.CACertificates { err := kongState.CACertificates.Add(CACertificate{ CACertificate: *c, }) if err != nil { return nil, errors.Wrap(err, "inserting ca_certificate into state") } } for _, p := range raw.Plugins { err := kongState.Plugins.Add(Plugin{Plugin: *p}) if err != nil { return nil, errors.Wrap(err, "inserting plugins into state") } } return kongState, nil } deck-1.4.0/state/cacert.go000066400000000000000000000070471400603563700153460ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/state/indexers" "github.com/kong/deck/utils" ) const ( caCertTableName = "caCert" ) var caCertTableSchema = &memdb.TableSchema{ Name: caCertTableName, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, "cert": { Name: "cert", Unique: true, Indexer: &indexers.MD5FieldsIndexer{ Fields: []string{"Cert"}, }, }, all: allIndex, }, } // CACertificatesCollection stores and indexes Kong CACertificates. type CACertificatesCollection collection // Add adds a caCert to the collection func (k *CACertificatesCollection) Add(caCert CACertificate) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(caCert.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() var searchBy []string searchBy = append(searchBy, *caCert.ID) if !utils.Empty(caCert.Cert) { searchBy = append(searchBy, *caCert.Cert) } _, err := getCACert(txn, searchBy...) if err == nil { return fmt.Errorf("inserting ca-cert %v: %w", caCert.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } err = txn.Insert(caCertTableName, &caCert) if err != nil { return err } txn.Commit() return nil } func getCACert(txn *memdb.Txn, IDs ...string) (*CACertificate, error) { for _, id := range IDs { res, err := multiIndexLookupUsingTxn(txn, caCertTableName, []string{"cert", "id"}, id) if err == ErrNotFound { continue } if err != nil { return nil, err } caCert, ok := res.(*CACertificate) if !ok { panic(unexpectedType) } return &CACertificate{CACertificate: *caCert.DeepCopy()}, nil } return nil, ErrNotFound } // Get gets a caCertificate by cert or ID. func (k *CACertificatesCollection) Get(certOrID string) (*CACertificate, error) { if certOrID == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() return getCACert(txn, certOrID) } // Update udpates an existing caCert. // It returns an error if the caCert is not already present. func (k *CACertificatesCollection) Update(caCert CACertificate) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(caCert.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteCACert(txn, *caCert.ID) if err != nil { return err } err = txn.Insert(caCertTableName, &caCert) if err != nil { return err } txn.Commit() return nil } func deleteCACert(txn *memdb.Txn, certOrID string) error { caCert, err := getCACert(txn, certOrID) if err != nil { return err } err = txn.Delete(caCertTableName, caCert) if err != nil { return err } return nil } // Delete deletes a caCertificate by looking up it's cert and key. func (k *CACertificatesCollection) Delete(certOrID string) error { if certOrID == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteCACert(txn, certOrID) if err != nil { return err } txn.Commit() return nil } // GetAll gets a caCertificate by name or ID. func (k *CACertificatesCollection) GetAll() ([]*CACertificate, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(caCertTableName, all, true) if err != nil { return nil, err } var res []*CACertificate for el := iter.Next(); el != nil; el = iter.Next() { c, ok := el.(*CACertificate) if !ok { panic(unexpectedType) } res = append(res, &CACertificate{CACertificate: *c.DeepCopy()}) } txn.Commit() return res, nil } deck-1.4.0/state/cacert_test.go000066400000000000000000000061001400603563700163720ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func caCertsCollection() *CACertificatesCollection { return state().CACertificates } func TestCACertificateInsert(t *testing.T) { assert := assert.New(t) collection := caCertsCollection() var caCert CACertificate assert.NotNil(collection.Add(caCert)) caCert.ID = kong.String("first") assert.NotNil(collection.Add(caCert)) caCert.Cert = kong.String("firstCert") assert.Nil(collection.Add(caCert)) // re-inesrt assert.NotNil(collection.Add(caCert)) } func TestCACertificateGetUpdate(t *testing.T) { assert := assert.New(t) collection := caCertsCollection() var caCert CACertificate assert.NotNil(collection.Update(caCert)) caCert.Cert = kong.String("firstCert") caCert.ID = kong.String("first") assert.NotNil(collection.Update(caCert)) err := collection.Add(caCert) assert.Nil(err) se, err := collection.Get("") assert.NotNil(err) assert.Nil(se) se, err = collection.Get("firstCert") assert.Nil(err) assert.NotNil(se) se.Cert = kong.String("firstCert-updated") err = collection.Update(*se) assert.Nil(err) se, err = collection.Get("firstCert-updated") assert.Nil(err) assert.NotNil(se) assert.Equal("firstCert-updated", *se.Cert) se, err = collection.Get("not-present") assert.Equal(ErrNotFound, err) assert.Nil(se) } func TestCACertInvalidType(t *testing.T) { assert := assert.New(t) collection := caCertsCollection() var cert Certificate cert.Cert = kong.String("my-cert") cert.ID = kong.String("first") txn := collection.db.Txn(true) txn.Insert(caCertTableName, &cert) txn.Commit() assert.Panics(func() { collection.Get("my-cert") }) assert.Panics(func() { collection.GetAll() }) } func TestCACertificateDelete(t *testing.T) { assert := assert.New(t) collection := caCertsCollection() assert.NotNil(collection.Delete("")) var caCert CACertificate caCert.ID = kong.String("first") caCert.Cert = kong.String("firstCert") err := collection.Add(caCert) assert.Nil(err) se, err := collection.Get("first") assert.Nil(err) assert.NotNil(se) assert.Equal("firstCert", *se.Cert) err = collection.Delete(*se.ID) assert.Nil(err) err = collection.Delete(*se.ID) assert.NotNil(err) caCert.ID = kong.String("first") caCert.Cert = kong.String("firstCert") err = collection.Add(caCert) assert.Nil(err) se, err = collection.Get("first") assert.Nil(err) assert.NotNil(se) assert.Equal("firstCert", *se.Cert) err = collection.Delete(*se.Cert) assert.Nil(err) err = collection.Delete(*se.ID) assert.NotNil(err) } func TestCACertificateGetAll(t *testing.T) { assert := assert.New(t) collection := caCertsCollection() var caCert CACertificate caCert.ID = kong.String("first") caCert.Cert = kong.String("firstCert") err := collection.Add(caCert) assert.Nil(err) var certificate2 CACertificate certificate2.ID = kong.String("second") certificate2.Cert = kong.String("secondCert") err = collection.Add(certificate2) assert.Nil(err) certificates, err := collection.GetAll() assert.Nil(err) assert.Equal(2, len(certificates)) } deck-1.4.0/state/certificate.go000066400000000000000000000116351400603563700163650ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/state/indexers" "github.com/kong/deck/utils" "github.com/pkg/errors" ) const ( certificateTableName = "certificate" ) var certificateTableSchema = &memdb.TableSchema{ Name: certificateTableName, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, "certkey": { Name: "certkey", Unique: true, Indexer: &indexers.MD5FieldsIndexer{ Fields: []string{"Cert", "Key"}, }, }, all: allIndex, }, } func validateCert(certificate Certificate) error { if utils.Empty(certificate.Key) { return errors.New("certificate's Key cannot be empty") } if utils.Empty(certificate.Cert) { return errors.New("certificate's Cert cannot be empty") } return nil } // CertificatesCollection stores and indexes Kong Certificates. type CertificatesCollection collection // Add adds a certificate to the collection func (k *CertificatesCollection) Add(certificate Certificate) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(certificate.ID) { return errIDRequired } if err := validateCert(certificate); err != nil { return err } txn := k.db.Txn(true) defer txn.Abort() _, err := getCertificate(txn, *certificate.ID) if err == nil { return fmt.Errorf("inserting certificate %v: %w", certificate.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } err = txn.Insert(certificateTableName, &certificate) if err != nil { return err } txn.Commit() return nil } func getCertificate(txn *memdb.Txn, id string) (*Certificate, error) { res, err := multiIndexLookupUsingTxn(txn, certificateTableName, []string{"id"}, id) if err != nil { return nil, err } c, ok := res.(*Certificate) if !ok { panic(unexpectedType) } return &Certificate{Certificate: *c.DeepCopy()}, nil } // Get gets a certificate by ID. func (k *CertificatesCollection) Get(id string) (*Certificate, error) { if id == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() certificate, err := getCertificate(txn, id) if err != nil { return nil, err } return certificate, nil } func getCertificateByCertKey(txn *memdb.Txn, cert, key string) (*Certificate, error) { res, err := txn.First(certificateTableName, "certkey", cert, key) if err != nil { return nil, err } if res == nil { return nil, ErrNotFound } c, ok := res.(*Certificate) if !ok { panic(unexpectedType) } return &Certificate{Certificate: *c.DeepCopy()}, nil } // GetByCertKey gets a certificate with // the same key and cert from the collection. func (k *CertificatesCollection) GetByCertKey(cert, key string) (*Certificate, error) { if cert == "" || key == "" { return nil, errors.New("cert/key cannot be empty string") } txn := k.db.Txn(false) defer txn.Abort() return getCertificateByCertKey(txn, cert, key) } // Update udpates an existing certificate. // It returns an error if the certificate is not already present. func (k *CertificatesCollection) Update(certificate Certificate) error { if utils.Empty(certificate.ID) { return errIDRequired } if err := validateCert(certificate); err != nil { return err } txn := k.db.Txn(true) defer txn.Abort() err := deleteCertificate(txn, *certificate.ID) if err != nil { return err } err = txn.Insert(certificateTableName, &certificate) if err != nil { return err } txn.Commit() return nil } func deleteCertificate(txn *memdb.Txn, id string) error { cert, err := getCertificate(txn, id) if err != nil { return err } err = txn.Delete(certificateTableName, cert) if err != nil { return err } return nil } // Delete deletes a certificate by ID. func (k *CertificatesCollection) Delete(id string) error { if id == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteCertificate(txn, id) if err != nil { return err } txn.Commit() return nil } // DeleteByCertKey deletes a certificate by looking up it's cert and key. func (k *CertificatesCollection) DeleteByCertKey(cert, key string) error { if cert == "" || key == "" { return errors.New("cert/key cannot be empty string") } txn := k.db.Txn(true) defer txn.Abort() certificate, err := getCertificateByCertKey(txn, cert, key) if err != nil { return err } err = deleteCertificate(txn, *certificate.ID) if err != nil { return err } txn.Commit() return nil } // GetAll gets a certificate by name or ID. func (k *CertificatesCollection) GetAll() ([]*Certificate, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(certificateTableName, all, true) if err != nil { return nil, err } var res []*Certificate for el := iter.Next(); el != nil; el = iter.Next() { c, ok := el.(*Certificate) if !ok { panic(unexpectedType) } res = append(res, &Certificate{Certificate: *c.DeepCopy()}) } txn.Commit() return res, nil } deck-1.4.0/state/certificate_test.go000066400000000000000000000117471400603563700174300ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func certsCollection() *CertificatesCollection { return state().Certificates } func TestCertificateInsert(t *testing.T) { assert := assert.New(t) collection := certsCollection() var certificate Certificate assert.NotNil(collection.Add(certificate)) certificate.ID = kong.String("first") assert.NotNil(collection.Add(certificate)) certificate.Key = kong.String("firstKey") assert.NotNil(collection.Add(certificate)) certificate.Cert = kong.String("firstCert") err := collection.Add(certificate) assert.Nil(err) // re-insert assert.NotNil(collection.Add(certificate)) } func TestCertificateGetUpdate(t *testing.T) { assert := assert.New(t) collection := certsCollection() var certificate Certificate certificate.Cert = kong.String("firstCert") certificate.Key = kong.String("firstKey") certificate.ID = kong.String("first") err := collection.Add(certificate) assert.Nil(err) se, err := collection.GetByCertKey("firstCert", "firstKey") assert.Nil(err) assert.NotNil(se) se.ID = nil assert.NotNil(collection.Update(*se)) se.ID = kong.String("first") se.Key = nil se.Cert = kong.String("firstCert-updated") err = collection.Update(*se) assert.NotNil(err) se.Key = kong.String("firstKey-updated") err = collection.Update(*se) assert.Nil(err) se, err = collection.Get("") assert.Nil(se) assert.NotNil(err) se, err = collection.GetByCertKey("firstCert-updated", "firstKey-updated") assert.Nil(err) assert.NotNil(se) assert.Equal("firstCert-updated", *se.Cert) se, err = collection.GetByCertKey("", "") assert.NotNil(err) assert.Nil(se) se, err = collection.GetByCertKey("not-present", "firstsdfsdfKey") assert.Equal(ErrNotFound, err) assert.Nil(se) } // Regression test // to ensure that the memory reference of the pointer returned by Get() // is different from the one stored in MemDB. func TestCertificateGetMemoryReference(t *testing.T) { assert := assert.New(t) collection := certsCollection() var cert Certificate cert.Cert = kong.String("my-cert") cert.Key = kong.String("my-key") cert.ID = kong.String("first") err := collection.Add(cert) assert.Nil(err) c, err := collection.Get("first") assert.Nil(err) assert.NotNil(c) c.Cert = kong.String("my-new-cert") c, err = collection.Get("first") assert.Nil(err) assert.NotNil(c) assert.Equal("my-cert", *c.Cert) } func TestCertificatesInvalidType(t *testing.T) { assert := assert.New(t) collection := certsCollection() var upstream Upstream upstream.Name = kong.String("my-upstream") upstream.ID = kong.String("first") txn := collection.db.Txn(true) err := txn.Insert(certificateTableName, &upstream) assert.NotNil(err) txn.Abort() type badCertificate struct { kong.Certificate Meta } certificate := badCertificate{ Certificate: kong.Certificate{ ID: kong.String("id"), Cert: kong.String("Cert"), Key: kong.String("Key"), }, } txn = collection.db.Txn(true) err = txn.Insert(certificateTableName, &certificate) assert.Nil(err) txn.Commit() assert.Panics(func() { collection.Get("id") }) assert.Panics(func() { collection.GetByCertKey("Cert", "Key") }) assert.Panics(func() { collection.GetAll() }) } func TestCertificateDelete(t *testing.T) { assert := assert.New(t) collection := certsCollection() var certificate Certificate certificate.ID = kong.String("first") certificate.Cert = kong.String("firstCert") certificate.Key = kong.String("firstKey") err := collection.Add(certificate) assert.Nil(err) se, err := collection.Get("first") assert.Nil(err) assert.NotNil(se) assert.Equal("firstCert", *se.Cert) err = collection.Delete(*se.ID) assert.Nil(err) err = collection.Delete(*se.ID) assert.NotNil(err) certificate.ID = kong.String("first") certificate.Cert = kong.String("firstCert") certificate.Key = kong.String("firstKey") err = collection.Add(certificate) assert.Nil(err) se, err = collection.Get("first") assert.Nil(err) assert.NotNil(se) assert.Equal("firstCert", *se.Cert) assert.NotNil(collection.DeleteByCertKey("", "")) assert.NotNil(collection.DeleteByCertKey("foo", "bar")) err = collection.DeleteByCertKey(*se.Cert, *se.Key) assert.Nil(err) err = collection.Delete("") assert.NotNil(err) err = collection.Delete(*se.ID) assert.NotNil(err) se, err = collection.Get("first") assert.NotNil(err) assert.Nil(se) } func TestCertificateGetAll(t *testing.T) { assert := assert.New(t) collection := certsCollection() var certificate Certificate certificate.ID = kong.String("first") certificate.Cert = kong.String("firstCert") certificate.Key = kong.String("firstKey") err := collection.Add(certificate) assert.Nil(err) var certificate2 Certificate certificate2.ID = kong.String("second") certificate2.Cert = kong.String("secondCert") certificate2.Key = kong.String("secondKey") err = collection.Add(certificate2) assert.Nil(err) certificates, err := collection.GetAll() assert.Nil(err) assert.Equal(2, len(certificates)) } deck-1.4.0/state/consumer.go000066400000000000000000000067661400603563700157470ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/utils" ) const ( consumerTableName = "consumer" ) var consumerTableSchema = &memdb.TableSchema{ Name: consumerTableName, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, "Username": { Name: "Username", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "Username"}, AllowMissing: true, }, all: allIndex, }, } // ConsumersCollection stores and indexes Kong Consumers. type ConsumersCollection collection // Add adds a consumer to the collection // An error is thrown if consumer.ID is empty. func (k *ConsumersCollection) Add(consumer Consumer) error { if utils.Empty(consumer.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() var searchBy []string searchBy = append(searchBy, *consumer.ID) if !utils.Empty(consumer.Username) { searchBy = append(searchBy, *consumer.Username) } _, err := getConsumer(txn, searchBy...) if err == nil { return fmt.Errorf("inserting consumer %v: %w", consumer.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } err = txn.Insert(consumerTableName, &consumer) if err != nil { return err } txn.Commit() return nil } func getConsumer(txn *memdb.Txn, IDs ...string) (*Consumer, error) { for _, id := range IDs { res, err := multiIndexLookupUsingTxn(txn, consumerTableName, []string{"Username", "id"}, id) if err == ErrNotFound { continue } if err != nil { return nil, err } consumer, ok := res.(*Consumer) if !ok { panic(unexpectedType) } return &Consumer{Consumer: *consumer.DeepCopy()}, nil } return nil, ErrNotFound } // Get gets a consumer by name or ID. func (k *ConsumersCollection) Get(userNameOrID string) (*Consumer, error) { if userNameOrID == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() return getConsumer(txn, userNameOrID) } // Update udpates an existing consumer. // It returns an error if the consumer is not already present. func (k *ConsumersCollection) Update(consumer Consumer) error { // TODO abstract this in the go-memdb library itself if utils.Empty(consumer.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteConsumer(txn, *consumer.ID) if err != nil { return err } err = txn.Insert(consumerTableName, &consumer) if err != nil { return err } txn.Commit() return nil } func deleteConsumer(txn *memdb.Txn, userNameOrID string) error { consumer, err := getConsumer(txn, userNameOrID) if err != nil { return err } err = txn.Delete(consumerTableName, consumer) if err != nil { return err } return nil } // Delete deletes a consumer by name or ID. func (k *ConsumersCollection) Delete(userNameOrID string) error { if userNameOrID == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteConsumer(txn, userNameOrID) if err != nil { return err } txn.Commit() return nil } // GetAll gets a consumer by name or ID. func (k *ConsumersCollection) GetAll() ([]*Consumer, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(consumerTableName, all, true) if err != nil { return nil, err } var res []*Consumer for el := iter.Next(); el != nil; el = iter.Next() { s, ok := el.(*Consumer) if !ok { panic(unexpectedType) } res = append(res, &Consumer{Consumer: *s.DeepCopy()}) } txn.Commit() return res, nil } deck-1.4.0/state/consumer_test.go000066400000000000000000000065441400603563700170000ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func consumersCollection() *ConsumersCollection { return state().Consumers } func TestConsumerInsert(t *testing.T) { assert := assert.New(t) collection := consumersCollection() var consumer Consumer assert.NotNil(collection.Add(consumer)) consumer.ID = kong.String("first") assert.Nil(collection.Add(consumer)) //re-insert consumer.Username = kong.String("my-name") assert.NotNil(collection.Add(consumer)) } func TestConsumerGetUpdate(t *testing.T) { assert := assert.New(t) collection := consumersCollection() var consumer Consumer consumer.ID = kong.String("first") consumer.Username = kong.String("my-name") err := collection.Add(consumer) assert.Nil(err) c, err := collection.Get("") assert.NotNil(err) assert.Nil(c) c, err = collection.Get("first") assert.Nil(err) assert.NotNil(c) c.ID = nil c.Username = kong.String("my-updated-name") err = collection.Update(*c) assert.NotNil(err) c.ID = kong.String("does-not-exist") assert.NotNil(collection.Update(*c)) c.ID = kong.String("first") assert.Nil(collection.Update(*c)) c, err = collection.Get("my-name") assert.NotNil(err) assert.Nil(c) c, err = collection.Get("my-updated-name") assert.Nil(err) assert.NotNil(c) } // Test to ensure that the memory reference of the pointer returned by Get() // is different from the one stored in MemDB. func TestConsumerGetMemoryReference(t *testing.T) { assert := assert.New(t) collection := consumersCollection() var consumer Consumer consumer.ID = kong.String("first") consumer.Username = kong.String("my-name") err := collection.Add(consumer) assert.Nil(err) c, err := collection.Get("first") assert.Nil(err) assert.NotNil(c) c.Username = kong.String("update-should-not-reflect") c, err = collection.Get("first") assert.Nil(err) assert.Equal("my-name", *c.Username) } func TestConsumersInvalidType(t *testing.T) { assert := assert.New(t) collection := consumersCollection() type c2 Consumer var c c2 c.Username = kong.String("my-name") c.ID = kong.String("first") txn := collection.db.Txn(true) assert.Nil(txn.Insert(consumerTableName, &c)) txn.Commit() assert.Panics(func() { collection.Get("my-name") }) assert.Panics(func() { collection.GetAll() }) } func TestConsumerDelete(t *testing.T) { assert := assert.New(t) collection := consumersCollection() var consumer Consumer consumer.ID = kong.String("first") consumer.Username = kong.String("my-consumer") err := collection.Add(consumer) assert.Nil(err) c, err := collection.Get("my-consumer") assert.Nil(err) assert.NotNil(c) assert.Equal("first", *c.ID) err = collection.Delete("first") assert.Nil(err) err = collection.Delete("") assert.NotNil(err) err = collection.Delete(*c.ID) assert.NotNil(err) } func TestConsumerGetAll(t *testing.T) { assert := assert.New(t) collection := consumersCollection() consumers := []Consumer{ { Consumer: kong.Consumer{ ID: kong.String("first"), Username: kong.String("my-consumer1"), }, }, { Consumer: kong.Consumer{ ID: kong.String("second"), Username: kong.String("my-consumer2"), }, }, } for _, s := range consumers { assert.Nil(collection.Add(s)) } allConsumers, err := collection.GetAll() assert.Nil(err) assert.Equal(len(consumers), len(allConsumers)) } deck-1.4.0/state/credentials.go000066400000000000000000000075741400603563700164070ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/state/indexers" ) const ( byConsumerID = "byConsumerID" ) // credentialsCollection stores and indexes key-auth credentials. type credentialsCollection struct { collection CredType string } func (k *credentialsCollection) TableName() string { return k.CredType } func (k *credentialsCollection) Schema() *memdb.TableSchema { return &memdb.TableSchema{ Name: k.CredType, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, byConsumerID: { Name: byConsumerID, Indexer: &indexers.MethodIndexer{ Method: "GetConsumer", }, }, "id2": { Name: "id2", // TODO configurable Unique: true, Indexer: &indexers.MethodIndexer{ Method: "GetID2", }, }, all: allIndex, }, } } func (k *credentialsCollection) getCred(txn *memdb.Txn, IDs ...string) (entity, error) { for _, id := range IDs { res, err := multiIndexLookupUsingTxn(txn, k.CredType, []string{"id", "id2"}, id) if err == ErrNotFound { continue } if err != nil { return nil, err } cred, ok := res.(entity) if !ok { panic(unexpectedType) } return cred, nil } return nil, ErrNotFound } // Add adds a key-auth credential to credentialsCollection. func (k *credentialsCollection) Add(cred entity) error { if cred.GetID() == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() // TODO detect unique constraint violation for ID2 _, err := k.getCred(txn, cred.GetID(), cred.GetID2()) if err == nil { return fmt.Errorf("inserting credential %v: %w", cred.GetID(), ErrAlreadyExists) } else if err != ErrNotFound { return err } err = txn.Insert(k.CredType, cred) if err != nil { return err } txn.Commit() return nil } // Get gets a credential by ID or endpoint key. func (k *credentialsCollection) Get(id string) (entity, error) { if id == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() return k.getCred(txn, id) } // Update updates an existing key-auth credential. func (k *credentialsCollection) Update(cred entity) error { // TODO abstract this check in the go-memdb library itself if cred.GetID() == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := k.deleteCred(txn, cred.GetID()) if err != nil { return err } err = txn.Insert(k.CredType, cred) if err != nil { return err } txn.Commit() return nil } func (k *credentialsCollection) deleteCred(txn *memdb.Txn, nameOrID string) error { cred, err := k.getCred(txn, nameOrID) if err != nil { return err } err = txn.Delete(k.CredType, cred) if err != nil { return err } return nil } // Delete deletes a key-auth credential by key or ID. func (k *credentialsCollection) Delete(id string) error { if id == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := k.deleteCred(txn, id) if err != nil { return err } txn.Commit() return nil } // GetAll gets all key-auth credentials. func (k *credentialsCollection) GetAll() ([]entity, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(k.CredType, all, true) if err != nil { return nil, err } var res []entity for el := iter.Next(); el != nil; el = iter.Next() { r, ok := el.(entity) if !ok { panic(unexpectedType) } res = append(res, r) } return res, nil } // GetAllByConsumerID returns all key-auth credentials // belong to a Consumer with id. func (k *credentialsCollection) GetAllByConsumerID(id string) ([]entity, error) { txn := k.db.Txn(false) iter, err := txn.Get(k.CredType, byConsumerID, id) if err != nil { return nil, err } var res []entity for el := iter.Next(); el != nil; el = iter.Next() { r, ok := el.(entity) if !ok { panic(unexpectedType) } res = append(res, r) } return res, nil } deck-1.4.0/state/hmacauth.go000066400000000000000000000040661400603563700156750ustar00rootroot00000000000000package state // HMACAuthsCollection stores and indexes hmac-auth credentials. type HMACAuthsCollection struct { credentialsCollection } func newHMACAuthsCollection(common collection) *HMACAuthsCollection { return &HMACAuthsCollection{ credentialsCollection: credentialsCollection{ collection: common, CredType: "hmac-auth", }, } } // Add adds a hmac-auth credential to HMACAuthsCollection func (k *HMACAuthsCollection) Add(hmacAuth HMACAuth) error { cred := (entity)(&hmacAuth) return k.credentialsCollection.Add(cred) } // Get gets a hmac-auth credential by key or ID. func (k *HMACAuthsCollection) Get(keyOrID string) (*HMACAuth, error) { cred, err := k.credentialsCollection.Get(keyOrID) if err != nil { return nil, err } hmacAuth, ok := cred.(*HMACAuth) if !ok { panic(unexpectedType) } return &HMACAuth{HMACAuth: *hmacAuth.DeepCopy()}, nil } // GetAllByConsumerID returns all hmac-auth credentials // belong to a Consumer with id. func (k *HMACAuthsCollection) GetAllByConsumerID(id string) ([]*HMACAuth, error) { creds, err := k.credentialsCollection.GetAllByConsumerID(id) if err != nil { return nil, err } var res []*HMACAuth for _, cred := range creds { r, ok := cred.(*HMACAuth) if !ok { panic(unexpectedType) } res = append(res, &HMACAuth{HMACAuth: *r.DeepCopy()}) } return res, nil } // Update updates an existing hmac-auth credential. func (k *HMACAuthsCollection) Update(hmacAuth HMACAuth) error { cred := (entity)(&hmacAuth) return k.credentialsCollection.Update(cred) } // Delete deletes a hmac-auth credential by key or ID. func (k *HMACAuthsCollection) Delete(keyOrID string) error { return k.credentialsCollection.Delete(keyOrID) } // GetAll gets all hmac-auth credentials. func (k *HMACAuthsCollection) GetAll() ([]*HMACAuth, error) { creds, err := k.credentialsCollection.GetAll() if err != nil { return nil, err } var res []*HMACAuth for _, cred := range creds { r, ok := cred.(*HMACAuth) if !ok { panic(unexpectedType) } res = append(res, &HMACAuth{HMACAuth: *r.DeepCopy()}) } return res, nil } deck-1.4.0/state/hmacauth_test.go000066400000000000000000000117151400603563700167330ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func hmacAuthsCollection() *HMACAuthsCollection { return state().HMACAuths } func TestHMACAuthInsert(t *testing.T) { assert := assert.New(t) collection := hmacAuthsCollection() var hmacAuth HMACAuth hmacAuth.ID = kong.String("first") err := collection.Add(hmacAuth) assert.NotNil(err) hmacAuth.Username = kong.String("my-username") err = collection.Add(hmacAuth) assert.NotNil(err) var hmacAuth2 HMACAuth hmacAuth2.Username = kong.String("my-username") hmacAuth2.ID = kong.String("first") hmacAuth2.Consumer = &kong.Consumer{ ID: kong.String("consumer-id"), Username: kong.String("my-username"), } err = collection.Add(hmacAuth2) assert.Nil(err) } func TestHMACAuthGet(t *testing.T) { assert := assert.New(t) collection := hmacAuthsCollection() var hmacAuth HMACAuth hmacAuth.Username = kong.String("my-username") hmacAuth.ID = kong.String("first") hmacAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(hmacAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-username", *res.Username) res, err = collection.Get("my-username") assert.Nil(err) assert.NotNil(res) assert.Equal("first", *res.ID) assert.Equal("consumer1-id", *res.Consumer.ID) res, err = collection.Get("does-not-exist") assert.NotNil(err) assert.Nil(res) } func TestHMACAuthUpdate(t *testing.T) { assert := assert.New(t) collection := hmacAuthsCollection() var hmacAuth HMACAuth hmacAuth.Username = kong.String("my-username") hmacAuth.ID = kong.String("first") hmacAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), } err := collection.Add(hmacAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-username", *res.Username) res.Username = kong.String("my-username2") res.Secret = kong.String("secret") err = collection.Update(*res) assert.Nil(err) res, err = collection.Get("my-username") assert.NotNil(err) assert.Nil(res) res, err = collection.Get("my-username2") assert.Nil(err) assert.Equal("first", *res.ID) assert.Equal("secret", *res.Secret) } func TestHMACAuthDelete(t *testing.T) { assert := assert.New(t) collection := hmacAuthsCollection() var hmacAuth HMACAuth hmacAuth.Username = kong.String("my-username1") hmacAuth.ID = kong.String("first") hmacAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(hmacAuth) assert.Nil(err) res, err := collection.Get("my-username1") assert.Nil(err) assert.NotNil(res) err = collection.Delete(*res.ID) assert.Nil(err) res, err = collection.Get("my-username1") assert.NotNil(err) assert.Nil(res) // delete a non-existing one err = collection.Delete("first") assert.NotNil(err) err = collection.Delete("my-username1") assert.NotNil(err) } func TestHMACAuthGetAll(t *testing.T) { assert := assert.New(t) collection := hmacAuthsCollection() populateWithHMACAuthFixtures(assert, collection) hmacAuths, err := collection.GetAll() assert.Nil(err) assert.Equal(5, len(hmacAuths)) } func TestHMACAuthGetByConsumer(t *testing.T) { assert := assert.New(t) collection := hmacAuthsCollection() populateWithHMACAuthFixtures(assert, collection) hmacAuths, err := collection.GetAllByConsumerID("consumer1-id") assert.Nil(err) assert.Equal(3, len(hmacAuths)) } func populateWithHMACAuthFixtures(assert *assert.Assertions, collection *HMACAuthsCollection) { hmacAuths := []HMACAuth{ { HMACAuth: kong.HMACAuth{ Username: kong.String("my-username11"), ID: kong.String("first"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { HMACAuth: kong.HMACAuth{ Username: kong.String("my-username12"), ID: kong.String("second"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { HMACAuth: kong.HMACAuth{ Username: kong.String("my-username13"), ID: kong.String("third"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { HMACAuth: kong.HMACAuth{ Username: kong.String("my-username21"), ID: kong.String("fourth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), Username: kong.String("consumer2-name"), }, }, }, { HMACAuth: kong.HMACAuth{ Username: kong.String("my-username22"), ID: kong.String("fifth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), Username: kong.String("consumer2-name"), }, }, }, } for _, k := range hmacAuths { err := collection.Add(k) assert.Nil(err) } } deck-1.4.0/state/indexers/000077500000000000000000000000001400603563700153675ustar00rootroot00000000000000deck-1.4.0/state/indexers/md5Indexer.go000066400000000000000000000024311400603563700177220ustar00rootroot00000000000000package indexers import ( "crypto/md5" "fmt" "reflect" ) // MD5FieldsIndexer is used to create an index based on md5sum of // string or *string fields. type MD5FieldsIndexer struct { // Fields to use for md5sum calculation Fields []string } // FromObject take Obj and returns index key formed using // the fields. func (s *MD5FieldsIndexer) FromObject(obj interface{}) (bool, []byte, error) { v := reflect.ValueOf(obj) v = reflect.Indirect(v) // Dereference the pointer if any blob := "" for _, field := range s.Fields { fv := v.FieldByName(field) fv = reflect.Indirect(fv) if !fv.IsValid() { return false, nil, fmt.Errorf("field '%s' for %#v is invalid", field, obj) } blob += fv.String() } if blob == "" { return false, nil, nil } md5Sum := md5.Sum([]byte(blob)) return true, md5Sum[:], nil } // FromArgs takes in a string and returns its byte form. func (s *MD5FieldsIndexer) FromArgs(args ...interface{}) ([]byte, error) { blob := "" for _, arg := range args { s, ok := arg.(string) if !ok { return nil, fmt.Errorf("argument must be a string: %#v", arg) } blob += s } if blob == "" { return nil, fmt.Errorf("empty args is not a valid value") } // Add the null character as a terminator md5Sum := md5.Sum([]byte(blob)) return md5Sum[:], nil } deck-1.4.0/state/indexers/md5Indexer_test.go000066400000000000000000000016331400603563700207640ustar00rootroot00000000000000package indexers import ( "crypto/md5" "testing" "github.com/stretchr/testify/assert" ) func TestMD5FieldsIndexer(t *testing.T) { assert := assert.New(t) type Foo struct { Bar *string Baz *string } in := &MD5FieldsIndexer{ Fields: []string{"Bar", "Baz"}, } s1 := "yolo" s2 := "oloy" b := Foo{ Bar: &s1, Baz: &s2, } ok, val, err := in.FromObject(b) assert.True(ok) assert.Nil(err) sum := md5.Sum([]byte("yolooloy")) assert.Equal(sum[:], val) val, err = in.FromArgs("yolo", "oloy") assert.Nil(err) assert.Equal(sum[:], val) ok, val, err = in.FromObject(Foo{}) assert.False(ok) assert.NotNil(err) assert.Empty(val) s1 = "" s2 = "" ok, val, err = in.FromObject(Foo{ Bar: &s1, Baz: &s2, }) assert.False(ok) assert.Nil(err) assert.Empty(val) val, err = in.FromArgs("") assert.NotNil(err) assert.Nil(val) val, err = in.FromArgs(2) assert.NotNil(err) assert.Nil(val) } deck-1.4.0/state/indexers/methodIndexer.go000066400000000000000000000022351400603563700205170ustar00rootroot00000000000000package indexers import ( "fmt" "reflect" ) // MethodIndexer is used to create an index based on a string returned // as a result of calling a method on the object. // It is assumed that the method has no arguments. type MethodIndexer struct { // Method name to call to get the string to bulid the index on. Method string } // FromObject take Obj and returns index key formed using // the fields. func (s *MethodIndexer) FromObject(obj interface{}) (bool, []byte, error) { v := reflect.ValueOf(obj) key := "" method := v.MethodByName(s.Method) resp := method.Call(nil) if len(resp) != 1 { return false, nil, fmt.Errorf("function call returned unexpected result") } key = resp[0].String() if key == "" { return false, nil, nil } return true, []byte(key), nil } // FromArgs takes in a string and returns its byte form. func (s *MethodIndexer) FromArgs(args ...interface{}) ([]byte, error) { blob := "" for _, arg := range args { s, ok := arg.(string) if !ok { return nil, fmt.Errorf("argument must be a string: %#v", arg) } blob += s } if blob == "" { return nil, fmt.Errorf("empty args is not a valid value") } return []byte(blob), nil } deck-1.4.0/state/indexers/methodIndexer_test.go000066400000000000000000000020731400603563700215560ustar00rootroot00000000000000package indexers import ( "testing" "github.com/stretchr/testify/assert" ) type Foo struct { id string } func (f Foo) ID() string { return f.id } func (f Foo) BadID() (string, error) { return f.id, nil } type ID interface { ID() string } func TestMethodIndexer(t *testing.T) { assert := assert.New(t) in := &MethodIndexer{ Method: "ID", } b := Foo{ id: "id1", } ok, val, err := in.FromObject(b) assert.True(ok) assert.Nil(err) assert.Equal([]byte("id1"), val) ok, val, err = in.FromObject(Foo{}) assert.False(ok) assert.Nil(err) assert.Empty(val) idInterface := (ID)(b) ok, val, err = in.FromObject(idInterface) assert.True(ok) assert.Nil(err) assert.Equal([]byte("id1"), val) val, err = in.FromArgs("id1") assert.Nil(err) assert.Equal([]byte("id1"), val) val, err = in.FromArgs("") assert.NotNil(err) assert.Nil(val) val, err = in.FromArgs(42) assert.NotNil(err) assert.Nil(val) in = &MethodIndexer{ Method: "BadID", } ok, val, err = in.FromObject(Foo{id: "id1"}) assert.False(ok) assert.NotNil(err) assert.Empty(val) } deck-1.4.0/state/indexers/subFieldIndexer.go000066400000000000000000000027661400603563700210050ustar00rootroot00000000000000package indexers import ( "fmt" "reflect" ) // Field represents a field that needs to be used for // subfield indexing. type Field struct { // Struct is the name of the field of the struct // being indexed. Struct string // Sub is the name of the field inside the struct Struct, // which is being indexed. Sub string } // SubFieldIndexer is used to extract a field from an object // using reflection and builds an index on that field. type SubFieldIndexer struct { Fields []Field } // FromObject take Obj and returns index key formed using // the field SubField. func (s *SubFieldIndexer) FromObject(obj interface{}) (bool, []byte, error) { v := reflect.ValueOf(obj) v = reflect.Indirect(v) // Dereference the pointer if any val := "" for _, f := range s.Fields { structV := v.FieldByName(f.Struct) structV = reflect.Indirect(structV) if !structV.IsValid() { continue } subField := structV.FieldByName(f.Sub) subField = reflect.Indirect(subField) val += subField.String() } if val == "" { return false, nil, nil } // Add the null character as a terminator val += "\x00" return true, []byte(val), nil } // FromArgs takes in a string and returns its byte form. func (s *SubFieldIndexer) FromArgs(args ...interface{}) ([]byte, error) { val := "" for _, arg := range args { s, ok := arg.(string) if !ok { return nil, fmt.Errorf("argument must be a string: %#v", args[0]) } val += s } // Add the null character as a terminator val += "\x00" return []byte(val), nil } deck-1.4.0/state/indexers/subFieldIndexer_test.go000066400000000000000000000026411400603563700220340ustar00rootroot00000000000000package indexers import ( "testing" "github.com/stretchr/testify/assert" ) func TestSubFieldIndexer(t *testing.T) { type Foo struct { Bar *string } type Baz struct { A *Foo } in := &SubFieldIndexer{ Fields: []Field{ { Struct: "A", Sub: "Bar", }, }, } s := "yolo" b := Baz{ A: &Foo{ Bar: &s, }, } ok, val, err := in.FromObject(b) assert := assert.New(t) assert.True(ok) assert.Nil(err) assert.Equal("yolo\x00", string(val)) ok, val, err = in.FromObject(Baz{}) assert.False(ok) assert.Nil(err) assert.Empty(val) s = "" ok, val, err = in.FromObject(Baz{ A: &Foo{ Bar: &s, }, }) assert.False(ok) assert.Nil(err) assert.Empty(val) val, err = in.FromArgs("yolo") assert.Nil(err) assert.Equal("yolo\x00", string(val)) val, err = in.FromArgs(2) assert.Nil(val) assert.NotNil(err) val, err = in.FromArgs("1", "2") assert.Equal([]byte("12\x00"), val) assert.Nil(err) } func TestSubFieldIndexerPointer(t *testing.T) { type Foo struct { Bar *string } type Baz struct { A *Foo } in := &SubFieldIndexer{ Fields: []Field{ { Struct: "A", Sub: "Bar", }, }, } s := "yolo" b := Baz{ A: &Foo{ Bar: &s, }, } ok, val, err := in.FromObject(b) assert := assert.New(t) assert.True(ok) assert.Nil(err) assert.Equal("yolo\x00", string(val)) val, err = in.FromArgs("yolo") assert.Nil(err) assert.Equal("yolo\x00", string(val)) } deck-1.4.0/state/jwtauth.go000066400000000000000000000040141400603563700155620ustar00rootroot00000000000000package state // JWTAuthsCollection stores and indexes jwt-auth credentials. type JWTAuthsCollection struct { credentialsCollection } func newJWTAuthsCollection(common collection) *JWTAuthsCollection { return &JWTAuthsCollection{ credentialsCollection: credentialsCollection{ collection: common, CredType: "jwt-auth", }, } } // Add adds a jwt-auth credential to JWTAuthsCollection func (k *JWTAuthsCollection) Add(jwtAuth JWTAuth) error { cred := (entity)(&jwtAuth) return k.credentialsCollection.Add(cred) } // Get gets a jwt-auth credential by key or ID. func (k *JWTAuthsCollection) Get(keyOrID string) (*JWTAuth, error) { cred, err := k.credentialsCollection.Get(keyOrID) if err != nil { return nil, err } jwtAuth, ok := cred.(*JWTAuth) if !ok { panic(unexpectedType) } return &JWTAuth{JWTAuth: *jwtAuth.DeepCopy()}, nil } // GetAllByConsumerID returns all jwt-auth credentials // belong to a Consumer with id. func (k *JWTAuthsCollection) GetAllByConsumerID(id string) ([]*JWTAuth, error) { creds, err := k.credentialsCollection.GetAllByConsumerID(id) if err != nil { return nil, err } var res []*JWTAuth for _, cred := range creds { r, ok := cred.(*JWTAuth) if !ok { panic(unexpectedType) } res = append(res, &JWTAuth{JWTAuth: *r.DeepCopy()}) } return res, nil } // Update updates an existing jwt-auth credential. func (k *JWTAuthsCollection) Update(jwtAuth JWTAuth) error { cred := (entity)(&jwtAuth) return k.credentialsCollection.Update(cred) } // Delete deletes a jwt-auth credential by key or ID. func (k *JWTAuthsCollection) Delete(keyOrID string) error { return k.credentialsCollection.Delete(keyOrID) } // GetAll gets all jwt-auth credentials. func (k *JWTAuthsCollection) GetAll() ([]*JWTAuth, error) { creds, err := k.credentialsCollection.GetAll() if err != nil { return nil, err } var res []*JWTAuth for _, cred := range creds { r, ok := cred.(*JWTAuth) if !ok { panic(unexpectedType) } res = append(res, &JWTAuth{JWTAuth: *r.DeepCopy()}) } return res, nil } deck-1.4.0/state/jwtauth_test.go000066400000000000000000000112061400603563700166220ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func jwtAuthsCollection() *JWTAuthsCollection { return state().JWTAuths } func TestJWTAuthInsert(t *testing.T) { assert := assert.New(t) collection := jwtAuthsCollection() var jwtAuth JWTAuth jwtAuth.Key = kong.String("my-key") jwtAuth.ID = kong.String("first") err := collection.Add(jwtAuth) assert.NotNil(err) var jwtAuth2 JWTAuth jwtAuth2.Key = kong.String("my-key") jwtAuth2.ID = kong.String("first") jwtAuth2.Consumer = &kong.Consumer{ ID: kong.String("consumer-id"), Username: kong.String("my-username"), } err = collection.Add(jwtAuth2) assert.Nil(err) } func TestJWTAuthGet(t *testing.T) { assert := assert.New(t) collection := jwtAuthsCollection() var jwtAuth JWTAuth jwtAuth.Key = kong.String("my-key") jwtAuth.ID = kong.String("first") jwtAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(jwtAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-key", *res.Key) res, err = collection.Get("my-key") assert.Nil(err) assert.NotNil(res) assert.Equal("first", *res.ID) assert.Equal("consumer1-id", *res.Consumer.ID) res, err = collection.Get("does-not-exist") assert.NotNil(err) assert.Nil(res) } func TestJWTAuthUpdate(t *testing.T) { assert := assert.New(t) collection := jwtAuthsCollection() var jwtAuth JWTAuth jwtAuth.Key = kong.String("my-key") jwtAuth.ID = kong.String("first") jwtAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(jwtAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-key", *res.Key) res.Key = kong.String("my-key2") err = collection.Update(*res) assert.Nil(err) res, err = collection.Get("my-key") assert.NotNil(err) assert.Nil(res) res, err = collection.Get("my-key2") assert.Nil(err) assert.Equal("first", *res.ID) } func TestJWTAuthDelete(t *testing.T) { assert := assert.New(t) collection := jwtAuthsCollection() var jwtAuth JWTAuth jwtAuth.Key = kong.String("my-key1") jwtAuth.ID = kong.String("first") jwtAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(jwtAuth) assert.Nil(err) res, err := collection.Get("my-key1") assert.Nil(err) assert.NotNil(res) err = collection.Delete(*res.ID) assert.Nil(err) res, err = collection.Get("my-key1") assert.NotNil(err) assert.Nil(res) // delete a non-existing one err = collection.Delete("first") assert.NotNil(err) err = collection.Delete("my-key1") assert.NotNil(err) } func TestJWTAuthGetAll(t *testing.T) { assert := assert.New(t) collection := jwtAuthsCollection() populateWithJWTAuthFixtures(assert, collection) jwtAuths, err := collection.GetAll() assert.Nil(err) assert.Equal(5, len(jwtAuths)) } func TestJWTAuthGetByConsumer(t *testing.T) { assert := assert.New(t) collection := jwtAuthsCollection() populateWithJWTAuthFixtures(assert, collection) jwtAuths, err := collection.GetAllByConsumerID("consumer1-id") assert.Nil(err) assert.Equal(3, len(jwtAuths)) } func populateWithJWTAuthFixtures(assert *assert.Assertions, collection *JWTAuthsCollection) { jwtAuths := []JWTAuth{ { JWTAuth: kong.JWTAuth{ Key: kong.String("my-key11"), ID: kong.String("first"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { JWTAuth: kong.JWTAuth{ Key: kong.String("my-key12"), ID: kong.String("second"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { JWTAuth: kong.JWTAuth{ Key: kong.String("my-key13"), ID: kong.String("third"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { JWTAuth: kong.JWTAuth{ Key: kong.String("my-key21"), ID: kong.String("fourth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), Username: kong.String("consumer2-name"), }, }, }, { JWTAuth: kong.JWTAuth{ Key: kong.String("my-key22"), ID: kong.String("fifth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), Username: kong.String("consumer2-name"), }, }, }, } for _, k := range jwtAuths { err := collection.Add(k) assert.Nil(err) } } deck-1.4.0/state/keyauth.go000066400000000000000000000040141400603563700155460ustar00rootroot00000000000000package state // KeyAuthsCollection stores and indexes key-auth credentials. type KeyAuthsCollection struct { credentialsCollection } func newKeyAuthsCollection(common collection) *KeyAuthsCollection { return &KeyAuthsCollection{ credentialsCollection: credentialsCollection{ collection: common, CredType: "key-auth", }, } } // Add adds a key-auth credential to KeyAuthsCollection func (k *KeyAuthsCollection) Add(keyAuth KeyAuth) error { cred := (entity)(&keyAuth) return k.credentialsCollection.Add(cred) } // Get gets a key-auth credential by key or ID. func (k *KeyAuthsCollection) Get(keyOrID string) (*KeyAuth, error) { cred, err := k.credentialsCollection.Get(keyOrID) if err != nil { return nil, err } keyAuth, ok := cred.(*KeyAuth) if !ok { panic(unexpectedType) } return &KeyAuth{KeyAuth: *keyAuth.DeepCopy()}, nil } // GetAllByConsumerID returns all key-auth credentials // belong to a Consumer with id. func (k *KeyAuthsCollection) GetAllByConsumerID(id string) ([]*KeyAuth, error) { creds, err := k.credentialsCollection.GetAllByConsumerID(id) if err != nil { return nil, err } var res []*KeyAuth for _, cred := range creds { r, ok := cred.(*KeyAuth) if !ok { panic(unexpectedType) } res = append(res, &KeyAuth{KeyAuth: *r.DeepCopy()}) } return res, nil } // Update updates an existing key-auth credential. func (k *KeyAuthsCollection) Update(keyAuth KeyAuth) error { cred := (entity)(&keyAuth) return k.credentialsCollection.Update(cred) } // Delete deletes a key-auth credential by key or ID. func (k *KeyAuthsCollection) Delete(keyOrID string) error { return k.credentialsCollection.Delete(keyOrID) } // GetAll gets all key-auth credentials. func (k *KeyAuthsCollection) GetAll() ([]*KeyAuth, error) { creds, err := k.credentialsCollection.GetAll() if err != nil { return nil, err } var res []*KeyAuth for _, cred := range creds { r, ok := cred.(*KeyAuth) if !ok { panic(unexpectedType) } res = append(res, &KeyAuth{KeyAuth: *r.DeepCopy()}) } return res, nil } deck-1.4.0/state/keyauth_test.go000066400000000000000000000125051400603563700166110ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func keyAuthsCollection() *KeyAuthsCollection { return state().KeyAuths } func TestKeyAuthInsert(t *testing.T) { assert := assert.New(t) collection := keyAuthsCollection() var keyAuth KeyAuth keyAuth.Key = kong.String("my-secret-apikey") keyAuth.ID = kong.String("first") err := collection.Add(keyAuth) assert.NotNil(err) var keyAuth2 KeyAuth keyAuth2.Key = kong.String("my-secret-apikey") keyAuth2.ID = kong.String("first") keyAuth2.Consumer = &kong.Consumer{ ID: kong.String("consumer-id"), } err = collection.Add(keyAuth2) assert.Nil(err) // same API key keyAuth2.Key = kong.String("my-secret-apikey") keyAuth2.ID = kong.String("second") keyAuth2.Consumer = &kong.Consumer{ ID: kong.String("consumer-id"), } err = collection.Add(keyAuth2) assert.NotNil(err) // re-insert err = collection.Add(keyAuth2) assert.NotNil(err) } func TestKeyAuthGet(t *testing.T) { assert := assert.New(t) collection := keyAuthsCollection() var keyAuth KeyAuth keyAuth.Key = kong.String("my-apikey") keyAuth.ID = kong.String("first") keyAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), } err := collection.Add(keyAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-apikey", *res.Key) res, err = collection.Get("my-apikey") assert.Nil(err) assert.NotNil(res) assert.Equal("first", *res.ID) assert.Equal("consumer1-id", *res.Consumer.ID) res, err = collection.Get("does-not-exist") assert.NotNil(err) assert.Nil(res) res, err = collection.Get("") assert.NotNil(err) assert.Nil(res) } func TestKeyAuthUpdate(t *testing.T) { assert := assert.New(t) collection := keyAuthsCollection() var keyAuth KeyAuth assert.NotNil(collection.Add(keyAuth)) keyAuth.Key = kong.String("my-apikey") keyAuth.ID = kong.String("first") keyAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), } err := collection.Add(keyAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-apikey", *res.Key) res.Key = kong.String("my-apikey2") err = collection.Update(*res) assert.Nil(err) res, err = collection.Get("first") assert.Nil(err) assert.Equal("my-apikey2", *res.Key) res, err = collection.Get("my-apikey") assert.NotNil(err) assert.Nil(res) } func TestKeyAuthDelete(t *testing.T) { assert := assert.New(t) collection := keyAuthsCollection() var keyAuth KeyAuth keyAuth.Key = kong.String("my-apikey1") keyAuth.ID = kong.String("first") keyAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), } err := collection.Add(keyAuth) assert.Nil(err) res, err := collection.Get("my-apikey1") assert.Nil(err) assert.NotNil(res) err = collection.Delete(*res.ID) assert.Nil(err) res, err = collection.Get("my-apikey1") assert.NotNil(err) assert.Nil(res) // delete a non-existing one err = collection.Delete("first") assert.NotNil(err) err = collection.Delete("my-apikey1") assert.NotNil(err) err = collection.Delete("does-not-exist") assert.NotNil(err) err = collection.Delete("") assert.NotNil(err) } func TestKeyAuthGetAll(t *testing.T) { assert := assert.New(t) collection := keyAuthsCollection() populateWithKeyAuthFixtures(assert, collection) keyAuths, err := collection.GetAll() assert.Nil(err) assert.Equal(5, len(keyAuths)) } func TestKeyAuthGetByConsumer(t *testing.T) { assert := assert.New(t) collection := keyAuthsCollection() populateWithKeyAuthFixtures(assert, collection) keyAuths, err := collection.GetAllByConsumerID("consumer1-id") assert.Nil(err) assert.Equal(3, len(keyAuths)) } func populateWithKeyAuthFixtures(assert *assert.Assertions, collection *KeyAuthsCollection) { keyAuths := []KeyAuth{ { KeyAuth: kong.KeyAuth{ Key: kong.String("my-apikey11"), ID: kong.String("first"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), }, }, }, { KeyAuth: kong.KeyAuth{ Key: kong.String("my-apikey12"), ID: kong.String("second"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), }, }, }, { KeyAuth: kong.KeyAuth{ Key: kong.String("my-apikey13"), ID: kong.String("third"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), }, }, }, { KeyAuth: kong.KeyAuth{ Key: kong.String("my-apikey21"), ID: kong.String("fourth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), }, }, }, { KeyAuth: kong.KeyAuth{ Key: kong.String("my-apikey22"), ID: kong.String("fifth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), }, }, }, } for _, k := range keyAuths { err := collection.Add(k) assert.Nil(err) } } func TestKeyAuthInvalidType(t *testing.T) { assert := assert.New(t) collection := keyAuthsCollection() var hmacAuth HMACAuth hmacAuth.Username = kong.String("my-hmacAuth") hmacAuth.ID = kong.String("first") hmacAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer-id"), } txn := collection.db.Txn(true) assert.Nil(txn.Insert("key-auth", &hmacAuth)) txn.Commit() assert.Panics(func() { collection.Get("first") }) assert.Panics(func() { collection.GetAll() }) assert.Panics(func() { collection.GetAllByConsumerID("consumer-id") }) } deck-1.4.0/state/mtlsauth.go000066400000000000000000000040241400603563700157360ustar00rootroot00000000000000package state // MTLSAuthsCollection stores and indexes mtls-auth credentials. type MTLSAuthsCollection struct { credentialsCollection } func newMTLSAuthsCollection(common collection) *MTLSAuthsCollection { return &MTLSAuthsCollection{ credentialsCollection: credentialsCollection{ collection: common, CredType: "mtls-auth", }, } } // Add adds a mtls-auth credential to MTLSAuthsCollection func (k *MTLSAuthsCollection) Add(mtlsAuth MTLSAuth) error { cred := (entity)(&mtlsAuth) return k.credentialsCollection.Add(cred) } // Get gets a mtls-auth credential by ID. func (k *MTLSAuthsCollection) Get(ID string) (*MTLSAuth, error) { cred, err := k.credentialsCollection.Get(ID) if err != nil { return nil, err } mtlsAuth, ok := cred.(*MTLSAuth) if !ok { panic(unexpectedType) } return &MTLSAuth{MTLSAuth: *mtlsAuth.DeepCopy()}, nil } // GetAllByConsumerID returns all mtls-auth credentials // belong to a Consumer with id. func (k *MTLSAuthsCollection) GetAllByConsumerID(id string) ([]*MTLSAuth, error) { creds, err := k.credentialsCollection.GetAllByConsumerID(id) if err != nil { return nil, err } var res []*MTLSAuth for _, cred := range creds { r, ok := cred.(*MTLSAuth) if !ok { panic(unexpectedType) } res = append(res, &MTLSAuth{MTLSAuth: *r.DeepCopy()}) } return res, nil } // Update updates an existing mtls-auth credential. func (k *MTLSAuthsCollection) Update(mtlsAuth MTLSAuth) error { cred := (entity)(&mtlsAuth) return k.credentialsCollection.Update(cred) } // Delete deletes a mtls-auth credential by ID. func (k *MTLSAuthsCollection) Delete(ID string) error { return k.credentialsCollection.Delete(ID) } // GetAll gets all mtls-auth credentials. func (k *MTLSAuthsCollection) GetAll() ([]*MTLSAuth, error) { creds, err := k.credentialsCollection.GetAll() if err != nil { return nil, err } var res []*MTLSAuth for _, cred := range creds { r, ok := cred.(*MTLSAuth) if !ok { panic(unexpectedType) } res = append(res, &MTLSAuth{MTLSAuth: *r.DeepCopy()}) } return res, nil } deck-1.4.0/state/mtlsauth_test.go000066400000000000000000000113751400603563700170040ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func mtlsAuthsCollection() *MTLSAuthsCollection { return state().MTLSAuths } func TestMTLSAuthInsert(t *testing.T) { assert := assert.New(t) collection := mtlsAuthsCollection() var mtlsAuth MTLSAuth mtlsAuth.ID = kong.String("first") err := collection.Add(mtlsAuth) assert.NotNil(err) mtlsAuth.SubjectName = kong.String("test@example.com") err = collection.Add(mtlsAuth) assert.NotNil(err) var mtlsAuth2 MTLSAuth mtlsAuth2.SubjectName = kong.String("test@example.com") mtlsAuth2.ID = kong.String("first") mtlsAuth2.Consumer = &kong.Consumer{ ID: kong.String("consumer-id"), Username: kong.String("my-username"), } err = collection.Add(mtlsAuth2) assert.Nil(err) } func TestMTLSAuthGet(t *testing.T) { assert := assert.New(t) collection := mtlsAuthsCollection() var mtlsAuth MTLSAuth mtlsAuth.SubjectName = kong.String("test@example.com") mtlsAuth.ID = kong.String("first") mtlsAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(mtlsAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("test@example.com", *res.SubjectName) res, err = collection.Get("does-not-exist") assert.NotNil(err) assert.Nil(res) } func TestMTLSAuthUpdate(t *testing.T) { assert := assert.New(t) collection := mtlsAuthsCollection() var mtlsAuth MTLSAuth mtlsAuth.SubjectName = kong.String("test@example.com") mtlsAuth.ID = kong.String("first") mtlsAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(mtlsAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("test@example.com", *res.SubjectName) res.SubjectName = kong.String("test2@example.com") err = collection.Update(*res) assert.Nil(err) res, err = collection.Get("first") assert.Nil(err) assert.Equal("test2@example.com", *res.SubjectName) } func TestMTLSAuthDelete(t *testing.T) { assert := assert.New(t) collection := mtlsAuthsCollection() var mtlsAuth MTLSAuth mtlsAuth.SubjectName = kong.String("test@example.com") mtlsAuth.ID = kong.String("first") mtlsAuth.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(mtlsAuth) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) err = collection.Delete(*res.ID) assert.Nil(err) res, err = collection.Get("first") assert.NotNil(err) assert.Nil(res) // delete a non-existing one err = collection.Delete("first") assert.NotNil(err) } func TestMTLSAuthGetAll(t *testing.T) { assert := assert.New(t) collection := mtlsAuthsCollection() populateWithMTLSAuthFixtures(assert, collection) mtlsAuths, err := collection.GetAll() assert.Nil(err) assert.Equal(5, len(mtlsAuths)) } func TestMTLSAuthGetByConsumer(t *testing.T) { assert := assert.New(t) collection := mtlsAuthsCollection() populateWithMTLSAuthFixtures(assert, collection) mtlsAuths, err := collection.GetAllByConsumerID("consumer1-id") assert.Nil(err) assert.Equal(3, len(mtlsAuths)) } func populateWithMTLSAuthFixtures(assert *assert.Assertions, collection *MTLSAuthsCollection) { mtlsAuths := []MTLSAuth{ { MTLSAuth: kong.MTLSAuth{ SubjectName: kong.String("test11@example.com"), ID: kong.String("first"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { MTLSAuth: kong.MTLSAuth{ SubjectName: kong.String("test12@example.com"), ID: kong.String("second"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { MTLSAuth: kong.MTLSAuth{ SubjectName: kong.String("test13@example.com"), ID: kong.String("third"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { MTLSAuth: kong.MTLSAuth{ SubjectName: kong.String("test21@example.com"), ID: kong.String("fourth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), Username: kong.String("consumer2-name"), }, }, }, { MTLSAuth: kong.MTLSAuth{ SubjectName: kong.String("test22@example.com"), ID: kong.String("fifth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), Username: kong.String("consumer2-name"), }, }, }, } for _, k := range mtlsAuths { err := collection.Add(k) assert.Nil(err) } } deck-1.4.0/state/oauth2.go000066400000000000000000000042601400603563700153010ustar00rootroot00000000000000package state // Oauth2CredsCollection stores and indexes oauth2 credentials. type Oauth2CredsCollection struct { credentialsCollection } func newOauth2CredsCollection(common collection) *Oauth2CredsCollection { return &Oauth2CredsCollection{ credentialsCollection: credentialsCollection{ collection: common, CredType: "oauth2", }, } } // Add adds a oauth2 credential to Oauth2CredsCollection func (k *Oauth2CredsCollection) Add(keyAuth Oauth2Credential) error { cred := (entity)(&keyAuth) return k.credentialsCollection.Add(cred) } // Get gets a oauth2 credential by key or ID. func (k *Oauth2CredsCollection) Get(keyOrID string) (*Oauth2Credential, error) { cred, err := k.credentialsCollection.Get(keyOrID) if err != nil { return nil, err } keyAuth, ok := cred.(*Oauth2Credential) if !ok { panic(unexpectedType) } return &Oauth2Credential{Oauth2Credential: *keyAuth.DeepCopy()}, nil } // GetAllByConsumerID returns all oauth2 credentials // belong to a Consumer with id. func (k *Oauth2CredsCollection) GetAllByConsumerID(id string) ([]*Oauth2Credential, error) { creds, err := k.credentialsCollection.GetAllByConsumerID(id) if err != nil { return nil, err } var res []*Oauth2Credential for _, cred := range creds { r, ok := cred.(*Oauth2Credential) if !ok { panic(unexpectedType) } res = append(res, &Oauth2Credential{Oauth2Credential: *r.DeepCopy()}) } return res, nil } // Update updates an existing oauth2 credential. func (k *Oauth2CredsCollection) Update(keyAuth Oauth2Credential) error { cred := (entity)(&keyAuth) return k.credentialsCollection.Update(cred) } // Delete deletes a oauth2 credential by key or ID. func (k *Oauth2CredsCollection) Delete(keyOrID string) error { return k.credentialsCollection.Delete(keyOrID) } // GetAll gets all oauth2 credentials. func (k *Oauth2CredsCollection) GetAll() ([]*Oauth2Credential, error) { creds, err := k.credentialsCollection.GetAll() if err != nil { return nil, err } var res []*Oauth2Credential for _, cred := range creds { r, ok := cred.(*Oauth2Credential) if !ok { panic(unexpectedType) } res = append(res, &Oauth2Credential{Oauth2Credential: *r.DeepCopy()}) } return res, nil } deck-1.4.0/state/oauth2_test.go000066400000000000000000000120221400603563700163330ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func oauth2CredsCollection() *Oauth2CredsCollection { return state().Oauth2Creds } func TestOauth2CredInsert(t *testing.T) { assert := assert.New(t) collection := oauth2CredsCollection() var oauth2Cred Oauth2Credential oauth2Cred.ClientID = kong.String("client-id") oauth2Cred.ID = kong.String("first") err := collection.Add(oauth2Cred) assert.NotNil(err) oauth2Cred.Consumer = &kong.Consumer{ ID: kong.String("consumer-id"), Username: kong.String("my-username"), } err = collection.Add(oauth2Cred) assert.Nil(err) } func TestOauth2CredentialGet(t *testing.T) { assert := assert.New(t) collection := oauth2CredsCollection() var oauth2Cred Oauth2Credential oauth2Cred.ClientID = kong.String("my-clientid") oauth2Cred.ID = kong.String("first") oauth2Cred.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(oauth2Cred) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-clientid", *res.ClientID) res, err = collection.Get("my-clientid") assert.Nil(err) assert.NotNil(res) assert.Equal("first", *res.ID) assert.Equal("consumer1-id", *res.Consumer.ID) res, err = collection.Get("does-not-exist") assert.NotNil(err) assert.Nil(res) } func TestOauth2CredentialUpdate(t *testing.T) { assert := assert.New(t) collection := oauth2CredsCollection() var oauth2Cred Oauth2Credential oauth2Cred.ClientID = kong.String("my-clientid") oauth2Cred.ID = kong.String("first") oauth2Cred.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(oauth2Cred) assert.Nil(err) res, err := collection.Get("first") assert.Nil(err) assert.NotNil(res) assert.Equal("my-clientid", *res.ClientID) res.ClientID = kong.String("my-clientid2") err = collection.Update(*res) assert.Nil(err) res, err = collection.Get("my-clientid") assert.NotNil(err) assert.Nil(res) res, err = collection.Get("my-clientid2") assert.Nil(err) assert.Equal("first", *res.ID) } func TestOauth2CredentialDelete(t *testing.T) { assert := assert.New(t) collection := oauth2CredsCollection() var oauth2Cred Oauth2Credential oauth2Cred.ClientID = kong.String("my-clientid1") oauth2Cred.ID = kong.String("first") oauth2Cred.Consumer = &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), } err := collection.Add(oauth2Cred) assert.Nil(err) res, err := collection.Get("my-clientid1") assert.Nil(err) assert.NotNil(res) err = collection.Delete(*res.ID) assert.Nil(err) res, err = collection.Get("my-clientid1") assert.NotNil(err) assert.Nil(res) // delete a non-existing one err = collection.Delete("first") assert.NotNil(err) err = collection.Delete("my-clientid1") assert.NotNil(err) } func TestOauth2CredentialGetAll(t *testing.T) { assert := assert.New(t) collection := oauth2CredsCollection() populateWithOauth2CredentialFixtures(assert, collection) oauth2Creds, err := collection.GetAll() assert.Nil(err) assert.Equal(5, len(oauth2Creds)) } func TestOauth2CredentialGetByConsumer(t *testing.T) { assert := assert.New(t) collection := oauth2CredsCollection() populateWithOauth2CredentialFixtures(assert, collection) oauth2Creds, err := collection.GetAllByConsumerID("consumer1-id") assert.Nil(err) assert.Equal(3, len(oauth2Creds)) } func populateWithOauth2CredentialFixtures(assert *assert.Assertions, collection *Oauth2CredsCollection) { oauth2Creds := []Oauth2Credential{ { Oauth2Credential: kong.Oauth2Credential{ ClientID: kong.String("my-clientid11"), ID: kong.String("first"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { Oauth2Credential: kong.Oauth2Credential{ ClientID: kong.String("my-clientid12"), ID: kong.String("second"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { Oauth2Credential: kong.Oauth2Credential{ ClientID: kong.String("my-clientid13"), ID: kong.String("third"), Consumer: &kong.Consumer{ ID: kong.String("consumer1-id"), Username: kong.String("consumer1-name"), }, }, }, { Oauth2Credential: kong.Oauth2Credential{ ClientID: kong.String("my-clientid21"), ID: kong.String("fourth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), Username: kong.String("consumer2-name"), }, }, }, { Oauth2Credential: kong.Oauth2Credential{ ClientID: kong.String("my-clientid22"), ID: kong.String("fifth"), Consumer: &kong.Consumer{ ID: kong.String("consumer2-id"), Username: kong.String("consumer2-name"), }, }, }, } for _, k := range oauth2Creds { err := collection.Add(k) assert.Nil(err) } } deck-1.4.0/state/plugin.go000066400000000000000000000166671400603563700154130ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/state/indexers" "github.com/kong/deck/utils" "github.com/pkg/errors" ) var ( errPluginNameRequired = errors.New("name of plugin required") ) const ( pluginTableName = "plugin" pluginsByServiceID = "pluginsByServiceID" pluginsByRouteID = "pluginsByRouteID" pluginsByConsumerID = "pluginsByConsumerID" ) var pluginTableSchema = &memdb.TableSchema{ Name: pluginTableName, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, "name": { Name: "name", Indexer: &memdb.StringFieldIndex{Field: "Name"}, }, all: allIndex, // foreign pluginsByServiceID: { Name: pluginsByServiceID, Indexer: &indexers.SubFieldIndexer{ Fields: []indexers.Field{ { Struct: "Service", Sub: "ID", }, }, }, AllowMissing: true, }, pluginsByRouteID: { Name: pluginsByRouteID, Indexer: &indexers.SubFieldIndexer{ Fields: []indexers.Field{ { Struct: "Route", Sub: "ID", }, }, }, AllowMissing: true, }, pluginsByConsumerID: { Name: pluginsByConsumerID, Indexer: &indexers.SubFieldIndexer{ Fields: []indexers.Field{ { Struct: "Consumer", 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 // of the association is present "fields": { Name: "fields", Indexer: &indexers.SubFieldIndexer{ Fields: []indexers.Field{ { Struct: "Plugin", Sub: "Name", }, { Struct: "Service", Sub: "ID", }, { Struct: "Route", Sub: "ID", }, { Struct: "Consumer", Sub: "ID", }, }, }, }, }, } // PluginsCollection stores and indexes Kong Services. type PluginsCollection collection // Add adds a plugin to PluginsCollection func (k *PluginsCollection) Add(plugin Plugin) error { txn := k.db.Txn(true) defer txn.Abort() err := insertPlugin(txn, plugin) if err != nil { return err } txn.Commit() return nil } func insertPlugin(txn *memdb.Txn, plugin Plugin) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(plugin.ID) { return errIDRequired } if utils.Empty(plugin.Name) { return errPluginNameRequired } // err out if plugin with same ID is present _, err := getPluginByID(txn, *plugin.ID) if err == nil { return fmt.Errorf("inserting plugin %v: %w", plugin.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } // err out if another plugin with exact same combination is present sID, rID, cID := "", "", "" if plugin.Service != nil && !utils.Empty(plugin.Service.ID) { sID = *plugin.Service.ID } if plugin.Route != nil && !utils.Empty(plugin.Route.ID) { rID = *plugin.Route.ID } if plugin.Consumer != nil && !utils.Empty(plugin.Consumer.ID) { cID = *plugin.Consumer.ID } _, err = getPluginBy(txn, *plugin.Name, sID, rID, cID) if err == nil { return fmt.Errorf("inserting plugin %v: %w", plugin.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } // all good err = txn.Insert(pluginTableName, &plugin) if err != nil { return err } return nil } func getPluginByID(txn *memdb.Txn, id string) (*Plugin, error) { res, err := multiIndexLookupUsingTxn(txn, pluginTableName, []string{"id"}, id) if err != nil { return nil, err } plugin, ok := res.(*Plugin) if !ok { panic(unexpectedType) } return &Plugin{Plugin: *plugin.DeepCopy()}, nil } // Get gets a plugin by id. func (k *PluginsCollection) Get(id string) (*Plugin, error) { if id == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() plugin, err := getPluginByID(txn, id) if err != nil { return nil, err } return plugin, nil } // GetAllByName returns all plugins of a specific type // (key-auth, ratelimiting, etc). func (k *PluginsCollection) GetAllByName(name string) ([]*Plugin, error) { return k.getAllPluginsBy("name", name) } func getPluginBy(txn *memdb.Txn, name, svcID, routeID, consumerID string) ( *Plugin, error) { if name == "" { return nil, errPluginNameRequired } res, err := txn.First(pluginTableName, "fields", name, svcID, routeID, consumerID) if err != nil { return nil, err } if res == nil { return nil, ErrNotFound } p, ok := res.(*Plugin) if !ok { panic(unexpectedType) } return &Plugin{Plugin: *p.DeepCopy()}, nil } // 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. // 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) (*Plugin, error) { txn := k.db.Txn(false) defer txn.Abort() return getPluginBy(txn, name, serviceID, routeID, consumerID) } func (k *PluginsCollection) getAllPluginsBy(index, identifier string) ( []*Plugin, error) { if identifier == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(pluginTableName, index, identifier) if err != nil { return nil, err } var res []*Plugin for el := iter.Next(); el != nil; el = iter.Next() { p, ok := el.(*Plugin) if !ok { panic(unexpectedType) } res = append(res, &Plugin{Plugin: *p.DeepCopy()}) } return res, nil } // GetAllByServiceID returns all plugins referencing a service // by its id. func (k *PluginsCollection) GetAllByServiceID(id string) ([]*Plugin, error) { return k.getAllPluginsBy(pluginsByServiceID, id) } // GetAllByRouteID returns all plugins referencing a service // by its id. func (k *PluginsCollection) GetAllByRouteID(id string) ([]*Plugin, error) { return k.getAllPluginsBy(pluginsByRouteID, id) } // GetAllByConsumerID returns all plugins referencing a consumer // by its id. func (k *PluginsCollection) GetAllByConsumerID(id string) ([]*Plugin, error) { return k.getAllPluginsBy(pluginsByConsumerID, id) } // Update updates a plugin func (k *PluginsCollection) Update(plugin Plugin) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(plugin.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deletePlugin(txn, *plugin.ID) if err != nil { return err } err = insertPlugin(txn, plugin) if err != nil { return err } txn.Commit() return nil } func deletePlugin(txn *memdb.Txn, id string) error { plugin, err := getPluginByID(txn, id) if err != nil { return err } return txn.Delete(pluginTableName, plugin) } // Delete deletes a plugin by ID. func (k *PluginsCollection) Delete(id string) error { if id == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deletePlugin(txn, id) if err != nil { return err } txn.Commit() return nil } // GetAll gets a plugin by name or ID. func (k *PluginsCollection) GetAll() ([]*Plugin, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(pluginTableName, all, true) if err != nil { return nil, err } var res []*Plugin for el := iter.Next(); el != nil; el = iter.Next() { p, ok := el.(*Plugin) if !ok { panic(unexpectedType) } res = append(res, &Plugin{Plugin: *p.DeepCopy()}) } return res, nil } deck-1.4.0/state/plugin_test.go000066400000000000000000000260161400603563700164370ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func pluginsCollection() *PluginsCollection { return state().Plugins } func TestPluginsCollection_Add(t *testing.T) { type args struct { plugin Plugin } tests := []struct { name string args args wantErr bool }{ { name: "errors when ID is nil", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ Name: kong.String("foo"), }, }, }, wantErr: true, }, { name: "errors when Name is nil", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("id1"), }, }, }, wantErr: true, }, { name: "inserts with a name and ID", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("id2"), Name: kong.String("bar-name"), }, }, }, wantErr: false, }, { name: "errors on re-insert when same ID is present", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("id3"), Name: kong.String("foo-name"), }, }, }, wantErr: true, }, { name: "errors on re-insert when id is present", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("id3"), Name: kong.String("foobar-name"), }, }, }, wantErr: true, }, { name: "errors on re-insert when when same association is present", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("id4-new"), Name: kong.String("key-auth"), Route: &kong.Route{ ID: kong.String("route1"), }, }, }, }, wantErr: true, }, { name: "errors on re-insert when when same (multiple) association is present", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("id5-new"), Name: kong.String("key-auth"), Route: &kong.Route{ ID: kong.String("route1"), }, Service: &kong.Service{ ID: kong.String("svc1"), }, }, }, }, wantErr: true, }, } k := pluginsCollection() plugin1 := Plugin{ Plugin: kong.Plugin{ ID: kong.String("id3"), Name: kong.String("foo-name"), }, } plugin2 := Plugin{ Plugin: kong.Plugin{ ID: kong.String("id4"), Name: kong.String("key-auth"), Route: &kong.Route{ ID: kong.String("route1"), }, }, } plugin3 := Plugin{ Plugin: kong.Plugin{ ID: kong.String("id5"), Name: kong.String("key-auth"), Route: &kong.Route{ ID: kong.String("route1"), }, Service: &kong.Service{ ID: kong.String("svc1"), }, }, } k.Add(plugin1) k.Add(plugin2) k.Add(plugin3) for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if err := k.Add(tt.args.plugin); (err != nil) != tt.wantErr { t.Errorf("PluginsCollection.Add() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestPluginsCollection_Update(t *testing.T) { type args struct { plugin Plugin } tests := []struct { name string args args wantErr bool }{ { name: "errors when ID is nil", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ Name: kong.String("foo"), }, }, }, wantErr: true, }, { name: "errors when Name is nil", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("id1"), }, }, }, wantErr: true, }, { name: "errors when the plugin is not present", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("does-not-exist-yet"), Name: kong.String("bar-name"), }, }, }, wantErr: true, }, { name: "updates when ID is present", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("id3"), Name: kong.String("foo-name-new"), }, }, }, wantErr: false, }, { name: "errors on update when when same association is present", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("new-id"), Name: kong.String("key-auth"), Route: &kong.Route{ ID: kong.String("route1"), }, }, }, }, wantErr: true, }, { name: "errors on update when when same (multiple) association is present", args: args{ plugin: Plugin{ Plugin: kong.Plugin{ ID: kong.String("new-id"), Name: kong.String("key-auth"), Route: &kong.Route{ ID: kong.String("route1"), }, Service: &kong.Service{ ID: kong.String("svc1"), }, }, }, }, wantErr: true, }, } k := pluginsCollection() plugin1 := Plugin{ Plugin: kong.Plugin{ ID: kong.String("id1"), Name: kong.String("foo-name"), }, } plugin2 := Plugin{ Plugin: kong.Plugin{ ID: kong.String("id2"), Name: kong.String("key-auth"), Route: &kong.Route{ ID: kong.String("route1"), }, }, } plugin3 := Plugin{ Plugin: kong.Plugin{ ID: kong.String("id3"), Name: kong.String("key-auth"), Route: &kong.Route{ ID: kong.String("route1"), }, Service: &kong.Service{ ID: kong.String("svc1"), }, }, } k.Add(plugin1) k.Add(plugin2) k.Add(plugin3) for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if err := k.Update(tt.args.plugin); (err != nil) != tt.wantErr { t.Errorf("PluginsCollection.Update() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestPluginGet(t *testing.T) { assert := assert.New(t) collection := pluginsCollection() var plugin Plugin plugin.Name = kong.String("my-plugin") plugin.ID = kong.String("first") plugin.Service = &kong.Service{ ID: kong.String("service1-id"), Name: kong.String("service1-name"), } assert.NotNil(plugin.Service) err := collection.Add(plugin) assert.NotNil(plugin.Service) assert.Nil(err) re, err := collection.Get("first") assert.Nil(err) assert.NotNil(re) assert.Equal("my-plugin", *re.Name) re.Service = &kong.Service{ ID: kong.String("service2-id"), Name: kong.String("service2-name"), } re, err = collection.Get("does-not-exists") assert.Equal(ErrNotFound, err) assert.Nil(re) } func TestGetPluginByProp(t *testing.T) { plugins := []Plugin{ { Plugin: kong.Plugin{ ID: kong.String("1"), Name: kong.String("key-auth"), Config: map[string]interface{}{ "key1": "value1", }, }, }, { Plugin: kong.Plugin{ ID: kong.String("2"), Name: kong.String("key-auth"), Service: &kong.Service{ ID: kong.String("svc1"), }, Config: map[string]interface{}{ "key2": "value2", }, }, }, { Plugin: kong.Plugin{ ID: kong.String("3"), Name: kong.String("key-auth"), Route: &kong.Route{ ID: kong.String("route1"), }, Config: map[string]interface{}{ "key3": "value3", }, }, }, { Plugin: kong.Plugin{ ID: kong.String("4"), Name: kong.String("key-auth"), Consumer: &kong.Consumer{ ID: kong.String("consumer1"), }, Config: map[string]interface{}{ "key4": "value4", }, }, }, } assert := assert.New(t) collection := pluginsCollection() for _, p := range plugins { assert.Nil(collection.Add(p)) } plugin, err := collection.GetByProp("", "", "", "") assert.Nil(plugin) assert.NotNil(err) plugin, err = collection.GetByProp("foo", "", "", "") assert.Nil(plugin) assert.Equal(ErrNotFound, err) plugin, err = collection.GetByProp("key-auth", "", "", "") assert.Nil(err) assert.NotNil(plugin) assert.Equal("value1", plugin.Config["key1"]) plugin, err = collection.GetByProp("key-auth", "svc1", "", "") assert.Nil(err) assert.NotNil(plugin) assert.Equal("value2", plugin.Config["key2"]) plugin, err = collection.GetByProp("key-auth", "", "route1", "") assert.Nil(err) assert.NotNil(plugin) assert.Equal("value3", plugin.Config["key3"]) plugin, err = collection.GetByProp("key-auth", "", "", "consumer1") assert.Nil(err) assert.NotNil(plugin) assert.Equal("value4", plugin.Config["key4"]) } func TestPluginsInvalidType(t *testing.T) { assert := assert.New(t) collection := pluginsCollection() var service Service service.Name = kong.String("my-service") service.ID = kong.String("first") txn := collection.db.Txn(true) txn.Insert(pluginTableName, &service) txn.Commit() assert.Panics(func() { collection.Get("first") }) assert.Panics(func() { collection.GetAll() }) } func TestPluginDelete(t *testing.T) { assert := assert.New(t) collection := pluginsCollection() var plugin Plugin plugin.ID = kong.String("first") plugin.Name = kong.String("my-plugin") plugin.Config = map[string]interface{}{ "foo": "bar", "baz": "bar", } plugin.Service = &kong.Service{ ID: kong.String("service1-id"), Name: kong.String("service1-name"), } err := collection.Add(plugin) assert.Nil(err) p, err := collection.Get("first") assert.Nil(err) assert.NotNil(p) assert.Equal("bar", p.Config["foo"]) err = collection.Delete(*p.ID) assert.Nil(err) err = collection.Delete(*p.ID) assert.NotNil(err) assert.NotNil(collection.Delete("")) } func TestPluginGetAll(t *testing.T) { assert := assert.New(t) collection := pluginsCollection() plugins := []*Plugin{ { Plugin: kong.Plugin{ ID: kong.String("first-id"), Name: kong.String("key-auth"), Service: &kong.Service{ ID: kong.String("service1-id"), Name: kong.String("service1-name"), }, Config: map[string]interface{}{ "foo": "bar", "baz": "bar", }, }, }, { Plugin: kong.Plugin{ ID: kong.String("second-id"), Name: kong.String("basic-auth"), Service: &kong.Service{ ID: kong.String("service1-id"), Name: kong.String("service1-name"), }, }, }, { Plugin: kong.Plugin{ ID: kong.String("third-id"), Name: kong.String("rate-limiting"), Route: &kong.Route{ ID: kong.String("route1-id"), Name: kong.String("route1-name"), }, }, }, { Plugin: kong.Plugin{ ID: kong.String("fourth-id"), Name: kong.String("key-auth"), Route: &kong.Route{ ID: kong.String("route1-id"), Name: kong.String("route1-name"), }, }, }, } for _, p := range plugins { assert.Nil(collection.Add(*p)) } allPlugins, err := collection.GetAll() assert.Nil(err) assert.Equal(len(plugins), len(allPlugins)) allPlugins, err = collection.GetAllByName("") assert.NotNil(err) assert.Nil(allPlugins) allPlugins, err = collection.GetAllByConsumerID("") assert.NotNil(err) assert.Nil(allPlugins) allPlugins, err = collection.GetAllByRouteID("") assert.NotNil(err) assert.Nil(allPlugins) allPlugins, err = collection.GetAllByServiceID("") assert.NotNil(err) assert.Nil(allPlugins) allPlugins, err = collection.GetAllByName("key-auth") assert.Nil(err) assert.Equal(2, len(allPlugins)) allPlugins, err = collection.GetAllByRouteID("route1-id") assert.Nil(err) assert.Equal(2, len(allPlugins)) allPlugins, err = collection.GetAllByServiceID("service1-id") assert.Nil(err) assert.Equal(2, len(allPlugins)) allPlugins, err = collection.GetAllByServiceID("service-nope") assert.Nil(err) assert.Equal(0, len(allPlugins)) } deck-1.4.0/state/route.go000066400000000000000000000101711400603563700152330ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/state/indexers" "github.com/kong/deck/utils" ) const ( routeTableName = "route" routesByServiceID = "routesByServiceID" ) var routeTableSchema = &memdb.TableSchema{ Name: routeTableName, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, "name": { Name: "name", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "Name"}, AllowMissing: true, }, all: allIndex, // foreign routesByServiceID: { Name: routesByServiceID, Indexer: &indexers.SubFieldIndexer{ Fields: []indexers.Field{ { Struct: "Service", Sub: "ID", }, }, }, AllowMissing: true, }, }, } // RoutesCollection stores and indexes Kong Routes. type RoutesCollection collection // Add adds a route into RoutesCollection // route.ID should not be nil else an error is thrown. func (k *RoutesCollection) Add(route Route) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(route.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() var searchBy []string searchBy = append(searchBy, *route.ID) if !utils.Empty(route.Name) { searchBy = append(searchBy, *route.Name) } _, err := getRoute(txn, searchBy...) if err == nil { return fmt.Errorf("inserting route %v: %w", route.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } err = txn.Insert(routeTableName, &route) if err != nil { return err } txn.Commit() return nil } func getRoute(txn *memdb.Txn, IDs ...string) (*Route, error) { for _, id := range IDs { res, err := multiIndexLookupUsingTxn(txn, routeTableName, []string{"name", "id"}, id) if err == ErrNotFound { continue } if err != nil { return nil, err } route, ok := res.(*Route) if !ok { panic(unexpectedType) } return &Route{Route: *route.DeepCopy()}, nil } return nil, ErrNotFound } // Get gets a route by name or ID. func (k *RoutesCollection) Get(nameOrID string) (*Route, error) { if nameOrID == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() route, err := getRoute(txn, nameOrID) if err != nil { return nil, err } return route, nil } // Update updates a route func (k *RoutesCollection) Update(route Route) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(route.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteRoute(txn, *route.ID) if err != nil { return err } err = txn.Insert(routeTableName, &route) if err != nil { return err } txn.Commit() return nil } func deleteRoute(txn *memdb.Txn, nameOrID string) error { route, err := getRoute(txn, nameOrID) if err != nil { return err } err = txn.Delete(routeTableName, route) if err != nil { return err } return nil } // Delete deletes a route by name or ID. func (k *RoutesCollection) Delete(nameOrID string) error { if nameOrID == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteRoute(txn, nameOrID) if err != nil { return err } txn.Commit() return nil } // GetAll gets a route by name or ID. func (k *RoutesCollection) GetAll() ([]*Route, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(routeTableName, all, true) if err != nil { return nil, err } var res []*Route for el := iter.Next(); el != nil; el = iter.Next() { r, ok := el.(*Route) if !ok { panic(unexpectedType) } res = append(res, &Route{Route: *r.DeepCopy()}) } txn.Commit() return res, nil } // GetAllByServiceID returns all routes referencing a service // by its id. func (k *RoutesCollection) GetAllByServiceID(id string) ([]*Route, error) { txn := k.db.Txn(false) iter, err := txn.Get(routeTableName, routesByServiceID, id) if err != nil { return nil, err } var res []*Route for el := iter.Next(); el != nil; el = iter.Next() { r, ok := el.(*Route) if !ok { panic(unexpectedType) } res = append(res, &Route{Route: *r.DeepCopy()}) } return res, nil } deck-1.4.0/state/route_test.go000066400000000000000000000215231400603563700162750ustar00rootroot00000000000000package state import ( "reflect" "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func routesCollection() *RoutesCollection { return state().Routes } func TestRoutesCollection_Add(t *testing.T) { type args struct { route Route } tests := []struct { name string args args wantErr bool }{ { name: "errors when ID is nil", args: args{ route: Route{ Route: kong.Route{ Name: kong.String("foo"), Hosts: kong.StringSlice("example.com"), }, }, }, wantErr: true, }, { name: "inserts without a name", args: args{ route: Route{ Route: kong.Route{ ID: kong.String("id1"), Hosts: kong.StringSlice("example.com"), }, }, }, wantErr: false, }, { name: "inserts with a name and ID", args: args{ route: Route{ Route: kong.Route{ ID: kong.String("id2"), Name: kong.String("bar-name"), Hosts: kong.StringSlice("example.com"), }, }, }, wantErr: false, }, { name: "errors on re-insert when name is present", args: args{ route: Route{ Route: kong.Route{ ID: kong.String("id4"), Name: kong.String("foo-name"), Hosts: kong.StringSlice("example.com"), }, }, }, wantErr: true, }, { name: "errors on re-insert when id is present", args: args{ route: Route{ Route: kong.Route{ ID: kong.String("id3"), Name: kong.String("foobar-name"), Hosts: kong.StringSlice("example.com"), }, }, }, wantErr: true, }, } k := routesCollection() route1 := Route{ Route: kong.Route{ ID: kong.String("id3"), Name: kong.String("foo-name"), Hosts: kong.StringSlice("example.com"), }, } k.Add(route1) for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if err := k.Add(tt.args.route); (err != nil) != tt.wantErr { t.Errorf("RoutesCollection.Add() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestRoutesCollection_Get(t *testing.T) { type args struct { nameOrID string } route1 := Route{ Route: kong.Route{ ID: kong.String("foo-id"), Hosts: kong.StringSlice("example.com"), }, } route2 := Route{ Route: kong.Route{ ID: kong.String("bar-id"), Name: kong.String("bar-name"), Hosts: kong.StringSlice("example.com"), }, } tests := []struct { name string args args want *Route wantErr bool }{ { name: "gets a route by ID", args: args{ nameOrID: "foo-id", }, want: &route1, wantErr: false, }, { name: "gets a route by Name", args: args{ nameOrID: "bar-name", }, want: &route2, wantErr: false, }, { name: "returns an ErrNotFound when no route found", args: args{ nameOrID: "baz-id", }, want: nil, wantErr: true, }, { name: "returns an error when ID is empty", args: args{ nameOrID: "", }, want: nil, wantErr: true, }, } k := routesCollection() k.Add(route1) k.Add(route2) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := k.Get(tt.args.nameOrID) if (err != nil) != tt.wantErr { t.Errorf("RoutesCollection.Get() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("RoutesCollection.Get() = %v, want %v", got, tt.want) } }) } } func TestRoutesInvalidType(t *testing.T) { assert := assert.New(t) collection := routesCollection() var service Service service.Name = kong.String("my-service") service.ID = kong.String("first") txn := collection.db.Txn(true) txn.Insert(routeTableName, &service) txn.Commit() assert.Panics(func() { collection.Get("my-service") }) assert.Panics(func() { collection.GetAll() }) } func TestRoutesCollection_Update(t *testing.T) { route1 := Route{ Route: kong.Route{ ID: kong.String("foo-id"), Hosts: kong.StringSlice("example.com"), }, } route2 := Route{ Route: kong.Route{ ID: kong.String("bar-id"), Name: kong.String("bar-name"), Hosts: kong.StringSlice("example.com"), }, } route3 := Route{ Route: kong.Route{ ID: kong.String("foo-id"), Name: kong.String("name"), Hosts: kong.StringSlice("example.com"), }, } type args struct { route Route } tests := []struct { name string args args wantErr bool updatedRoute *Route }{ { name: "update errors if route.ID is nil", args: args{ route: Route{ Route: kong.Route{ Name: kong.String("name"), }, }, }, wantErr: true, }, { name: "update errors if route does not exist", args: args{ route: Route{ Route: kong.Route{ ID: kong.String("does-not-exist"), }, }, }, wantErr: true, }, { name: "update succeeds when ID is supplied", args: args{ route: route3, }, wantErr: false, updatedRoute: &route3, }, } k := routesCollection() k.Add(route1) k.Add(route2) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { //t.Parallel() if err := k.Update(tt.args.route); (err != nil) != tt.wantErr { t.Errorf("RoutesCollection.Update() error = %v, wantErr %v", err, tt.wantErr) } if !tt.wantErr { got, _ := k.Get(*tt.updatedRoute.ID) if !reflect.DeepEqual(got, tt.updatedRoute) { t.Errorf("update route, got = %#v, want %#v", got, tt.updatedRoute) } } }) } } // Regression test // to ensure that the memory reference of the pointer returned by Get() // is different from the one stored in MemDB. func TestRouteGetMemoryReference(t *testing.T) { assert := assert.New(t) collection := routesCollection() var route Route route.Name = kong.String("my-route") route.ID = kong.String("first") route.Hosts = kong.StringSlice("example.com", "demo.example.com") route.Service = &kong.Service{ ID: kong.String("service1-id"), } assert.NotNil(route.Service) err := collection.Add(route) assert.NotNil(route.Service) assert.Nil(err) re, err := collection.Get("first") assert.Nil(err) assert.NotNil(re) assert.Equal("my-route", *re.Name) re.SNIs = kong.StringSlice("example.com", "demo.example.com") re, err = collection.Get("my-route") assert.Nil(err) assert.NotNil(re) assert.Nil(re.SNIs) } func TestRouteDelete(t *testing.T) { assert := assert.New(t) collection := routesCollection() var route Route route.Name = kong.String("my-route") route.ID = kong.String("first") route.Hosts = kong.StringSlice("example.com", "demo.example.com") route.Service = &kong.Service{ ID: kong.String("service1-id"), } err := collection.Add(route) assert.Nil(err) re, err := collection.Get("my-route") assert.Nil(err) assert.NotNil(re) assert.Equal("example.com", *re.Hosts[0]) err = collection.Delete(*re.ID) assert.Nil(err) err = collection.Delete(*re.ID) assert.NotNil(err) } func TestRouteGetAll(t *testing.T) { assert := assert.New(t) collection := routesCollection() var route Route route.Name = kong.String("my-route1") route.ID = kong.String("first") route.Hosts = kong.StringSlice("example.com", "demo.example.com") route.Service = &kong.Service{ ID: kong.String("service1-id"), } err := collection.Add(route) assert.Nil(err) var route2 Route route2.Name = kong.String("my-route2") route2.ID = kong.String("second") route2.Hosts = kong.StringSlice("example.com", "demo.example.com") route2.Service = &kong.Service{ ID: kong.String("service1-id"), } err = collection.Add(route2) assert.Nil(err) routes, err := collection.GetAll() assert.Nil(err) assert.Equal(2, len(routes)) } func TestRouteGetAllByServiceID(t *testing.T) { assert := assert.New(t) collection := routesCollection() routes := []*Route{ { Route: kong.Route{ ID: kong.String("route0-id"), }, }, { Route: kong.Route{ ID: kong.String("route1-id"), Name: kong.String("route1-name"), Service: &kong.Service{ ID: kong.String("service1-id"), }, }, }, { Route: kong.Route{ ID: kong.String("route2-id"), Service: &kong.Service{ ID: kong.String("service1-id"), }, }, }, { Route: kong.Route{ ID: kong.String("route3-id"), Name: kong.String("route3-name"), Service: &kong.Service{ ID: kong.String("service2-id"), }, }, }, { Route: kong.Route{ ID: kong.String("route4-id"), Name: kong.String("route4-name"), Service: &kong.Service{ ID: kong.String("service2-id"), }, }, }, { Route: kong.Route{ ID: kong.String("route5-id"), Service: &kong.Service{ ID: kong.String("service2-id"), }, }, }, } for _, route := range routes { err := collection.Add(*route) assert.Nil(err) } routes, err := collection.GetAllByServiceID("service1-id") assert.Nil(err) assert.Equal(2, len(routes)) routes, err = collection.GetAllByServiceID("service2-id") assert.Nil(err) assert.Equal(3, len(routes)) } deck-1.4.0/state/service.go000066400000000000000000000067141400603563700155450ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/utils" ) const ( serviceTableName = "service" ) var serviceTableSchema = &memdb.TableSchema{ Name: serviceTableName, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, "name": { Name: "name", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "Name"}, AllowMissing: true, }, all: allIndex, }, } // ServicesCollection stores and indexes Kong Services. type ServicesCollection collection // Add adds a service to the collection. // service.ID should not be nil else an error is thrown. func (k *ServicesCollection) Add(service Service) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(service.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() var searchBy []string searchBy = append(searchBy, *service.ID) if !utils.Empty(service.Name) { searchBy = append(searchBy, *service.Name) } _, err := getService(txn, searchBy...) if err == nil { return fmt.Errorf("inserting service %v: %w", service.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } err = txn.Insert(serviceTableName, &service) if err != nil { return err } txn.Commit() return nil } func getService(txn *memdb.Txn, IDs ...string) (*Service, error) { for _, id := range IDs { res, err := multiIndexLookupUsingTxn(txn, serviceTableName, []string{"name", "id"}, id) if err == ErrNotFound { continue } if err != nil { return nil, err } service, ok := res.(*Service) if !ok { panic(unexpectedType) } return &Service{Service: *service.DeepCopy()}, nil } return nil, ErrNotFound } // Get gets a service by name or ID. func (k *ServicesCollection) Get(nameOrID string) (*Service, error) { if nameOrID == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() return getService(txn, nameOrID) } // Update udpates an existing service. // It returns an error if the service is not already present. func (k *ServicesCollection) Update(service Service) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(service.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteService(txn, *service.ID) if err != nil { return err } err = txn.Insert(serviceTableName, &service) if err != nil { return err } txn.Commit() return nil } func deleteService(txn *memdb.Txn, nameOrID string) error { service, err := getService(txn, nameOrID) if err != nil { return err } err = txn.Delete(serviceTableName, service) if err != nil { return err } return nil } // Delete deletes a service by name or ID. func (k *ServicesCollection) Delete(nameOrID string) error { if nameOrID == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteService(txn, nameOrID) if err != nil { return err } txn.Commit() return nil } // GetAll returns all the services. func (k *ServicesCollection) GetAll() ([]*Service, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(serviceTableName, all, true) if err != nil { return nil, err } var res []*Service for el := iter.Next(); el != nil; el = iter.Next() { s, ok := el.(*Service) if !ok { panic(unexpectedType) } res = append(res, &Service{Service: *s.DeepCopy()}) } txn.Commit() return res, nil } deck-1.4.0/state/service_test.go000066400000000000000000000211041400603563700165720ustar00rootroot00000000000000package state import ( "reflect" "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func servicesCollection() *ServicesCollection { return state().Services } func TestServicesCollection_Add(t *testing.T) { type args struct { service Service } tests := []struct { name string args args wantErr bool }{ { name: "errors when ID is nil", args: args{ service: Service{ Service: kong.Service{ Name: kong.String("foo"), Host: kong.String("example.com"), }, }, }, wantErr: true, }, { name: "inserts without a name", args: args{ service: Service{ Service: kong.Service{ ID: kong.String("id1"), Host: kong.String("example.com"), }, }, }, wantErr: false, }, { name: "inserts with a name and ID", args: args{ service: Service{ Service: kong.Service{ ID: kong.String("id2"), Name: kong.String("foo-name"), Host: kong.String("example.com"), }, }, }, wantErr: false, }, { name: "errors on re-insert by ID", args: args{ service: Service{ Service: kong.Service{ ID: kong.String("id3"), Name: kong.String("foo-name"), Host: kong.String("example.com"), }, }, }, wantErr: true, }, { name: "errors on re-insert by Name", args: args{ service: Service{ Service: kong.Service{ ID: kong.String("new-id"), Name: kong.String("bar-name"), Host: kong.String("example.com"), }, }, }, wantErr: true, }, } k := servicesCollection() svc1 := Service{ Service: kong.Service{ ID: kong.String("id3"), Name: kong.String("bar-name"), Host: kong.String("example.com"), }, } k.Add(svc1) for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if err := k.Add(tt.args.service); (err != nil) != tt.wantErr { t.Errorf("ServicesCollection.Add() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestServicesCollection_Get(t *testing.T) { type args struct { nameOrID string } svc1 := Service{ Service: kong.Service{ ID: kong.String("foo-id"), Host: kong.String("example.com"), }, } svc2 := Service{ Service: kong.Service{ ID: kong.String("bar-id"), Name: kong.String("bar-name"), Host: kong.String("example.com"), }, } tests := []struct { name string args args want *Service wantErr bool }{ { name: "gets a service by ID", args: args{ nameOrID: "foo-id", }, want: &svc1, wantErr: false, }, { name: "gets a service by Name", args: args{ nameOrID: "bar-name", }, want: &svc2, wantErr: false, }, { name: "returns an ErrNotFound when no service found", args: args{ nameOrID: "baz-id", }, want: nil, wantErr: true, }, { name: "returns an error when ID is empty", args: args{ nameOrID: "", }, want: nil, wantErr: true, }, } k := servicesCollection() k.Add(svc1) k.Add(svc2) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := k.Get(tt.args.nameOrID) if (err != nil) != tt.wantErr { t.Errorf("ServicesCollection.Get() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("ServicesCollection.Get() = %v, want %v", got, tt.want) } }) } } func TestServicesCollection_Update(t *testing.T) { svc1 := Service{ Service: kong.Service{ ID: kong.String("foo-id"), Host: kong.String("example.com"), }, } svc2 := Service{ Service: kong.Service{ ID: kong.String("bar-id"), Name: kong.String("bar-name"), Host: kong.String("example.com"), }, } svc3 := Service{ Service: kong.Service{ ID: kong.String("foo-id"), Name: kong.String("name"), Host: kong.String("2.example.com"), Port: kong.Int(42), }, } type args struct { service Service } tests := []struct { name string args args wantErr bool updatedService *Service }{ { name: "update errors if service.ID is nil", args: args{ service: Service{ Service: kong.Service{ Name: kong.String("name"), }, }, }, wantErr: true, }, { name: "update errors if service does not exist", args: args{ service: Service{ Service: kong.Service{ ID: kong.String("does-not-exist"), }, }, }, wantErr: true, }, { name: "update succeeds when ID is supplied", args: args{ service: svc3, }, wantErr: false, updatedService: &svc3, }, } k := servicesCollection() k.Add(svc1) k.Add(svc2) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { //t.Parallel() if err := k.Update(tt.args.service); (err != nil) != tt.wantErr { t.Errorf("ServicesCollection.Update() error = %v, wantErr %v", err, tt.wantErr) } if !tt.wantErr { got, _ := k.Get(*tt.updatedService.ID) if !reflect.DeepEqual(got, tt.updatedService) { t.Errorf("update service, got = %#v, want %#v", got, tt.updatedService) } } }) } } func TestServiceUpdate(t *testing.T) { assert := assert.New(t) k := servicesCollection() svc1 := Service{ Service: kong.Service{ ID: kong.String("foo-id"), Name: kong.String("foo-name"), Host: kong.String("example.com"), }, } assert.Nil(k.Add(svc1)) svc1.Name = kong.String("bar-name") assert.Nil(k.Update(svc1)) r, err := k.Get("foo-id") assert.Nil(err) assert.NotNil(r) r, err = k.Get("bar-name") assert.Nil(err) assert.NotNil(r) r, err = k.Get("foo-name") assert.NotNil(err) assert.Nil(r) } // Regression test // to ensure that the memory reference of the pointer returned by Get() // is different from the one stored in MemDB. func TestServiceGetMemoryReference(t *testing.T) { assert := assert.New(t) collection := servicesCollection() var service Service service.Name = kong.String("my-service") service.ID = kong.String("first") err := collection.Add(service) assert.Nil(err) se, err := collection.Get("first") assert.Nil(err) assert.NotNil(se) se.Host = kong.String("example.com") se, err = collection.Get("my-service") assert.Nil(err) assert.NotNil(se) assert.Nil(se.Host) } func TestServicesInvalidType(t *testing.T) { assert := assert.New(t) collection := servicesCollection() var route Route route.Name = kong.String("my-route") route.ID = kong.String("first") txn := collection.db.Txn(true) txn.Insert(serviceTableName, &route) txn.Commit() assert.Panics(func() { collection.Get("my-route") }) assert.Panics(func() { collection.GetAll() }) } func TestServiceDelete(t *testing.T) { assert := assert.New(t) collection := servicesCollection() var service Service service.ID = kong.String("first") service.Host = kong.String("example.com") err := collection.Add(service) assert.Nil(err) err = collection.Delete("does-not-exist") assert.NotNil(err) err = collection.Delete("first") assert.Nil(err) err = collection.Delete("first") assert.NotNil(err) err = collection.Delete("") assert.NotNil(err) } func TestServiceGetAll(t *testing.T) { assert := assert.New(t) collection := servicesCollection() services := []Service{ { Service: kong.Service{ ID: kong.String("first"), Name: kong.String("my-service1"), Host: kong.String("example.com"), }, }, { Service: kong.Service{ ID: kong.String("second"), Name: kong.String("my-service2"), Host: kong.String("example.com"), }, }, } for _, s := range services { assert.Nil(collection.Add(s)) } allServices, err := collection.GetAll() assert.Nil(err) assert.Equal(len(services), len(allServices)) } // Regression test // to ensure that the memory reference of the pointer returned by Get() // is different from the one stored in MemDB. func TestServiceGetAllMemoryReference(t *testing.T) { assert := assert.New(t) collection := servicesCollection() services := []Service{ { Service: kong.Service{ ID: kong.String("first"), Name: kong.String("my-service1"), Host: kong.String("example.com"), }, }, { Service: kong.Service{ ID: kong.String("second"), Name: kong.String("my-service2"), Host: kong.String("example.com"), }, }, } for _, s := range services { assert.Nil(collection.Add(s)) } allServices, err := collection.GetAll() assert.Nil(err) assert.Equal(len(services), len(allServices)) allServices[0].Host = kong.String("new.example.com") allServices[1].Host = kong.String("new.example.com") service, err := collection.Get("my-service1") assert.Nil(err) assert.NotNil(service) assert.Equal("example.com", *service.Host) } deck-1.4.0/state/sni.go000066400000000000000000000103641400603563700146720ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/state/indexers" "github.com/kong/deck/utils" "github.com/pkg/errors" ) const ( sniTableName = "sni" snisByCertID = "snisByCertID" ) var errInvalidCert = errors.New("certificate.ID is required in sni") var sniTableSchema = &memdb.TableSchema{ Name: sniTableName, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, "name": { Name: "name", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "Name"}, AllowMissing: true, }, all: allIndex, // foreign snisByCertID: { Name: snisByCertID, Indexer: &indexers.SubFieldIndexer{ Fields: []indexers.Field{ { Struct: "Certificate", Sub: "ID", }, }, }, }, }, } func validateCertForSNI(sni *SNI) error { if sni.Certificate == nil || utils.Empty(sni.Certificate.ID) { return errInvalidCert } return nil } // SNIsCollection stores and indexes Kong SNIs. type SNIsCollection collection // Add adds a sni into SNIsCollection // sni.ID should not be nil else an error is thrown. func (k *SNIsCollection) Add(sni SNI) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(sni.ID) { return errIDRequired } if err := validateCertForSNI(&sni); err != nil { return err } txn := k.db.Txn(true) defer txn.Abort() var searchBy []string searchBy = append(searchBy, *sni.ID) if !utils.Empty(sni.Name) { searchBy = append(searchBy, *sni.Name) } _, err := getSNI(txn, searchBy...) if err == nil { return fmt.Errorf("inserting sni %v: %w", sni.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } err = txn.Insert(sniTableName, &sni) if err != nil { return err } txn.Commit() return nil } func getSNI(txn *memdb.Txn, IDs ...string) (*SNI, error) { for _, id := range IDs { res, err := multiIndexLookupUsingTxn(txn, sniTableName, []string{"name", "id"}, id) if err == ErrNotFound { continue } if err != nil { return nil, err } sni, ok := res.(*SNI) if !ok { panic(unexpectedType) } return &SNI{SNI: *sni.DeepCopy()}, nil } return nil, ErrNotFound } // Get gets a sni by name or ID. func (k *SNIsCollection) Get(nameOrID string) (*SNI, error) { if nameOrID == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() sni, err := getSNI(txn, nameOrID) if err != nil { return nil, err } return sni, nil } // Update updates a sni func (k *SNIsCollection) Update(sni SNI) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(sni.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteSNI(txn, *sni.ID) if err != nil { return err } err = txn.Insert(sniTableName, &sni) if err != nil { return err } txn.Commit() return nil } func deleteSNI(txn *memdb.Txn, nameOrID string) error { sni, err := getSNI(txn, nameOrID) if err != nil { return err } err = txn.Delete(sniTableName, sni) if err != nil { return err } return nil } // Delete deletes a sni by name or ID. func (k *SNIsCollection) Delete(nameOrID string) error { if nameOrID == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteSNI(txn, nameOrID) if err != nil { return err } txn.Commit() return nil } // GetAll gets a sni by name or ID. func (k *SNIsCollection) GetAll() ([]*SNI, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(sniTableName, all, true) if err != nil { return nil, err } var res []*SNI for el := iter.Next(); el != nil; el = iter.Next() { r, ok := el.(*SNI) if !ok { panic(unexpectedType) } res = append(res, &SNI{SNI: *r.DeepCopy()}) } txn.Commit() return res, nil } // GetAllByCertID returns all routes referencing a service // by its id. func (k *SNIsCollection) GetAllByCertID(id string) ([]*SNI, error) { txn := k.db.Txn(false) iter, err := txn.Get(sniTableName, snisByCertID, id) if err != nil { return nil, err } var res []*SNI for el := iter.Next(); el != nil; el = iter.Next() { r, ok := el.(*SNI) if !ok { panic(unexpectedType) } res = append(res, &SNI{SNI: *r.DeepCopy()}) } return res, nil } deck-1.4.0/state/sni_test.go000066400000000000000000000220541400603563700157300ustar00rootroot00000000000000package state import ( "reflect" "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func snisCollection() *SNIsCollection { return state().SNIs } func TestSNIsCollection_Add(t *testing.T) { type args struct { sni SNI } tests := []struct { name string args args wantErr bool }{ { name: "errors when ID is nil", args: args{ sni: SNI{ SNI: kong.SNI{ Name: kong.String("foo"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, }, }, wantErr: true, }, { name: "inserts without a name", args: args{ sni: SNI{ SNI: kong.SNI{ ID: kong.String("id1"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, }, }, wantErr: false, }, { name: "inserts with a name and ID", args: args{ sni: SNI{ SNI: kong.SNI{ ID: kong.String("id2"), Name: kong.String("bar-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, }, }, wantErr: false, }, { name: "errors on re-insert when name is present", args: args{ sni: SNI{ SNI: kong.SNI{ ID: kong.String("id4"), Name: kong.String("foo-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, }, }, wantErr: true, }, { name: "errors on re-insert when id is present", args: args{ sni: SNI{ SNI: kong.SNI{ ID: kong.String("id3"), Name: kong.String("foobar-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, }, }, wantErr: true, }, { name: "errors on re-insert when id is present", args: args{ sni: SNI{ SNI: kong.SNI{ ID: kong.String("id3"), Name: kong.String("foobar-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, }, }, wantErr: true, }, } k := snisCollection() sni1 := SNI{ SNI: kong.SNI{ ID: kong.String("id3"), Name: kong.String("foo-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, } k.Add(sni1) for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if err := k.Add(tt.args.sni); (err != nil) != tt.wantErr { t.Errorf("SNIsCollection.Add() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestSNIsCollection_Get(t *testing.T) { type args struct { nameOrID string } sni1 := SNI{ SNI: kong.SNI{ ID: kong.String("foo-id"), Name: kong.String("foo-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, } sni2 := SNI{ SNI: kong.SNI{ ID: kong.String("bar-id"), Name: kong.String("bar-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, } tests := []struct { name string args args want *SNI wantErr bool }{ { name: "gets a sni by ID", args: args{ nameOrID: "foo-id", }, want: &sni1, wantErr: false, }, { name: "gets a sni by Name", args: args{ nameOrID: "bar-name", }, want: &sni2, wantErr: false, }, { name: "returns an ErrNotFound when no sni found", args: args{ nameOrID: "baz-id", }, want: nil, wantErr: true, }, { name: "returns an error when ID is empty", args: args{ nameOrID: "", }, want: nil, wantErr: true, }, } k := snisCollection() k.Add(sni1) k.Add(sni2) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := k.Get(tt.args.nameOrID) if (err != nil) != tt.wantErr { t.Errorf("SNIsCollection.Get() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SNIsCollection.Get() = %v, want %v", got, tt.want) } }) } } func TestSNIsInvalidType(t *testing.T) { assert := assert.New(t) collection := snisCollection() type derivedSNI struct { SNI } var sni derivedSNI sni.SNI = SNI{ SNI: kong.SNI{ ID: kong.String("foo-id"), Name: kong.String("foo-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, } txn := collection.db.Txn(true) txn.Insert(sniTableName, &sni) txn.Commit() assert.Panics(func() { collection.Get("foo-id") }) assert.Panics(func() { collection.GetAll() }) } func TestSNIsCollection_Update(t *testing.T) { sni1 := SNI{ SNI: kong.SNI{ ID: kong.String("foo-id"), Name: kong.String("foo-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, } sni2 := SNI{ SNI: kong.SNI{ ID: kong.String("bar-id"), Name: kong.String("bar-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, } sni3 := SNI{ SNI: kong.SNI{ ID: kong.String("foo-id"), Name: kong.String("name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, } type args struct { sni SNI } tests := []struct { name string args args wantErr bool updatedSNI *SNI }{ { name: "update errors if sni.ID is nil", args: args{ sni: SNI{ SNI: kong.SNI{ Name: kong.String("name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, }, }, wantErr: true, }, { name: "update errors if sni does not exist", args: args{ sni: SNI{ SNI: kong.SNI{ ID: kong.String("does-not-exist"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, }, }, wantErr: true, }, { name: "update succeeds when ID is supplied", args: args{ sni: sni3, }, wantErr: false, updatedSNI: &sni3, }, } k := snisCollection() k.Add(sni1) k.Add(sni2) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { //t.Parallel() if err := k.Update(tt.args.sni); (err != nil) != tt.wantErr { t.Errorf("SNIsCollection.Update() error = %v, wantErr %v", err, tt.wantErr) } if !tt.wantErr { got, _ := k.Get(*tt.updatedSNI.ID) if !reflect.DeepEqual(got, tt.updatedSNI) { t.Errorf("update sni, got = %#v, want %#v", got, tt.updatedSNI) } } }) } } // Regression test // to ensure that the memory reference of the pointer returned by Get() // is different from the one stored in MemDB. func TestSNIGetMemoryReference(t *testing.T) { assert := assert.New(t) collection := snisCollection() var sni SNI sni.Name = kong.String("my-sni") sni.ID = kong.String("first") sni.Certificate = &kong.Certificate{ ID: kong.String("cert1-id"), } err := collection.Add(sni) assert.Nil(err) re, err := collection.Get("first") assert.Nil(err) assert.NotNil(re) assert.Equal("my-sni", *re.Name) re, err = collection.Get("my-sni") assert.Nil(err) assert.NotNil(re) } func TestSNIDelete(t *testing.T) { assert := assert.New(t) collection := snisCollection() var sni SNI sni.Name = kong.String("my-sni") sni.ID = kong.String("first") sni.Certificate = &kong.Certificate{ ID: kong.String("cert1-id"), } err := collection.Add(sni) assert.Nil(err) re, err := collection.Get("my-sni") assert.Nil(err) assert.NotNil(re) assert.Equal("first", *re.ID) err = collection.Delete(*re.ID) assert.Nil(err) err = collection.Delete(*re.ID) assert.NotNil(err) } func TestSNIGetAll(t *testing.T) { assert := assert.New(t) collection := snisCollection() var sni SNI sni.Name = kong.String("my-sni1") sni.ID = kong.String("first") sni.Certificate = &kong.Certificate{ ID: kong.String("cert1-id"), } err := collection.Add(sni) assert.Nil(err) var sni2 SNI sni2.Name = kong.String("my-sni2") sni2.ID = kong.String("second") sni2.Certificate = &kong.Certificate{ ID: kong.String("cert1-id"), } err = collection.Add(sni2) assert.Nil(err) snis, err := collection.GetAll() assert.Nil(err) assert.Equal(2, len(snis)) } func TestSNIGetAllByServiceID(t *testing.T) { assert := assert.New(t) collection := snisCollection() snis := []*SNI{ { SNI: kong.SNI{ ID: kong.String("sni1-id"), Name: kong.String("sni1-name"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, }, { SNI: kong.SNI{ ID: kong.String("sni2-id"), Certificate: &kong.Certificate{ ID: kong.String("cert1-id"), }, }, }, { SNI: kong.SNI{ ID: kong.String("sni3-id"), Name: kong.String("sni3-name"), Certificate: &kong.Certificate{ ID: kong.String("cert2-id"), }, }, }, { SNI: kong.SNI{ ID: kong.String("sni4-id"), Name: kong.String("sni4-name"), Certificate: &kong.Certificate{ ID: kong.String("cert2-id"), }, }, }, { SNI: kong.SNI{ ID: kong.String("sni5-id"), Certificate: &kong.Certificate{ ID: kong.String("cert2-id"), }, }, }, } for _, sni := range snis { err := collection.Add(*sni) assert.Nil(err) } snis, err := collection.GetAllByCertID("cert1-id") assert.Nil(err) assert.Equal(2, len(snis)) snis, err = collection.GetAllByCertID("cert2-id") assert.Nil(err) assert.Equal(3, len(snis)) } deck-1.4.0/state/state.go000066400000000000000000000063011400603563700152150ustar00rootroot00000000000000package state import ( memdb "github.com/hashicorp/go-memdb" "github.com/pkg/errors" ) type collection struct { db *memdb.MemDB } // KongState is an in-memory database representation // of Kong's configuration. type KongState struct { common collection Services *ServicesCollection Routes *RoutesCollection Upstreams *UpstreamsCollection Targets *TargetsCollection Certificates *CertificatesCollection SNIs *SNIsCollection CACertificates *CACertificatesCollection Plugins *PluginsCollection Consumers *ConsumersCollection KeyAuths *KeyAuthsCollection HMACAuths *HMACAuthsCollection JWTAuths *JWTAuthsCollection BasicAuths *BasicAuthsCollection ACLGroups *ACLGroupsCollection Oauth2Creds *Oauth2CredsCollection MTLSAuths *MTLSAuthsCollection } // NewKongState creates a new in-memory KongState. func NewKongState() (*KongState, error) { // TODO FIXME clean up the mess keyAuthTemp := newKeyAuthsCollection(collection{}) hmacAuthTemp := newHMACAuthsCollection(collection{}) basicAuthTemp := newBasicAuthsCollection(collection{}) jwtAuthTemp := newJWTAuthsCollection(collection{}) oauth2CredsTemp := newOauth2CredsCollection(collection{}) mtlsAuthTemp := newMTLSAuthsCollection(collection{}) var schema = &memdb.DBSchema{ Tables: map[string]*memdb.TableSchema{ serviceTableName: serviceTableSchema, routeTableName: routeTableSchema, upstreamTableName: upstreamTableSchema, targetTableName: targetTableSchema, certificateTableName: certificateTableSchema, sniTableName: sniTableSchema, caCertTableName: caCertTableSchema, pluginTableName: pluginTableSchema, consumerTableName: consumerTableSchema, keyAuthTemp.TableName(): keyAuthTemp.Schema(), hmacAuthTemp.TableName(): hmacAuthTemp.Schema(), basicAuthTemp.TableName(): basicAuthTemp.Schema(), jwtAuthTemp.TableName(): jwtAuthTemp.Schema(), oauth2CredsTemp.TableName(): oauth2CredsTemp.Schema(), mtlsAuthTemp.TableName(): mtlsAuthTemp.Schema(), aclGroupTableName: aclGroupTableSchema, }, } memDB, err := memdb.NewMemDB(schema) if err != nil { return nil, errors.Wrap(err, "creating new ServiceCollection") } var state KongState state.common = collection{ db: memDB, } state.Services = (*ServicesCollection)(&state.common) state.Routes = (*RoutesCollection)(&state.common) state.Upstreams = (*UpstreamsCollection)(&state.common) state.Targets = (*TargetsCollection)(&state.common) state.Certificates = (*CertificatesCollection)(&state.common) state.SNIs = (*SNIsCollection)(&state.common) state.CACertificates = (*CACertificatesCollection)(&state.common) state.Plugins = (*PluginsCollection)(&state.common) state.Consumers = (*ConsumersCollection)(&state.common) state.KeyAuths = newKeyAuthsCollection(state.common) state.HMACAuths = newHMACAuthsCollection(state.common) state.BasicAuths = newBasicAuthsCollection(state.common) state.JWTAuths = newJWTAuthsCollection(state.common) state.Oauth2Creds = newOauth2CredsCollection(state.common) state.MTLSAuths = newMTLSAuthsCollection(state.common) state.ACLGroups = (*ACLGroupsCollection)(&state.common) return &state, nil } deck-1.4.0/state/state_test.go000066400000000000000000000004611400603563700162550ustar00rootroot00000000000000package state import ( "testing" "github.com/stretchr/testify/assert" ) func TestNewState(t *testing.T) { state, err := NewKongState() assert := assert.New(t) assert.Nil(err) assert.NotNil(state) } func state() *KongState { s, err := NewKongState() if err != nil { panic(err) } return s } deck-1.4.0/state/target.go000066400000000000000000000121551400603563700153670ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/state/indexers" "github.com/kong/deck/utils" "github.com/pkg/errors" ) const ( targetTableName = "target" targetsByUpstreamID = "targetsByUpstreamID" ) var errInvalidUpstream = errors.New("upstream.ID is required in target") var targetTableSchema = &memdb.TableSchema{ Name: targetTableName, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, "target": { Name: "target", Indexer: &indexers.SubFieldIndexer{ Fields: []indexers.Field{ { Struct: "Target", Sub: "Target", }, }, }, }, all: allIndex, // foreign targetsByUpstreamID: { Name: targetsByUpstreamID, Indexer: &indexers.SubFieldIndexer{ Fields: []indexers.Field{ { Struct: "Upstream", Sub: "ID", }, }, }, }, }, } func validateUpstream(target *Target) error { if target.Upstream == nil || utils.Empty(target.Upstream.ID) { return errInvalidUpstream } return nil } // TargetsCollection stores and indexes Kong Upstreams. type TargetsCollection collection // Add adds a target to TargetsCollection. // target should have an ID, Target and it's upstream's ID is set. func (k *TargetsCollection) Add(target Target) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(target.ID) { return errIDRequired } if err := validateUpstream(&target); err != nil { return err } txn := k.db.Txn(true) defer txn.Abort() var searchBy []string searchBy = append(searchBy, *target.ID) if !utils.Empty(target.Target.Target) { searchBy = append(searchBy, *target.Target.Target) } _, err := getTarget(txn, *target.Upstream.ID, searchBy...) if err == nil { return fmt.Errorf("inserting target %v: %w", target.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } err = txn.Insert(targetTableName, &target) if err != nil { return err } txn.Commit() return nil } func getTarget(txn *memdb.Txn, upstreamID string, IDs ...string) (*Target, error) { targets, err := getAllByUpstreamID(txn, upstreamID) if err != nil { return nil, err } for _, id := range IDs { for _, target := range targets { if id == *target.ID || id == *target.Target.Target { return &Target{Target: *target.DeepCopy()}, nil } } } return nil, ErrNotFound } func getAllByUpstreamID(txn *memdb.Txn, upstreamID string) ([]*Target, error) { iter, err := txn.Get(targetTableName, targetsByUpstreamID, upstreamID) if err != nil { return nil, err } var targets []*Target for el := iter.Next(); el != nil; el = iter.Next() { t, ok := el.(*Target) if !ok { panic(unexpectedType) } targets = append(targets, &Target{Target: *t.DeepCopy()}) } return targets, nil } // Get returns a specific target for upstream with upstreamID. func (k *TargetsCollection) Get(upstreamID, targetOrID string) (*Target, error) { txn := k.db.Txn(false) defer txn.Abort() return getTarget(txn, upstreamID, targetOrID) } // Update updates a target func (k *TargetsCollection) Update(target Target) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(target.ID) { return errIDRequired } if err := validateUpstream(&target); err != nil { return err } txn := k.db.Txn(true) defer txn.Abort() // This doesn't follow the usual getTarget() because // the target.Upstream.ID can be different from the one in the DB. res, err := multiIndexLookupUsingTxn(txn, targetTableName, []string{"id"}, *target.ID) if err != nil { return err } t, ok := res.(*Target) if !ok { panic(unexpectedType) } err = txn.Delete(targetTableName, *t) if err != nil { return err } err = txn.Insert(targetTableName, &target) if err != nil { return err } txn.Commit() return nil } func deleteTarget(txn *memdb.Txn, upstreamID, targetOrID string) error { target, err := getTarget(txn, upstreamID, targetOrID) if err != nil { return err } err = txn.Delete(targetTableName, target) if err != nil { return err } return nil } // Delete deletes a target by its ID. func (k *TargetsCollection) Delete(upstreamID, targetOrID string) error { if targetOrID == "" { return errIDRequired } if upstreamID == "" { return errInvalidUpstream } txn := k.db.Txn(true) defer txn.Abort() err := deleteTarget(txn, upstreamID, targetOrID) if err != nil { return err } txn.Commit() return nil } // GetAll gets a target by Target or ID. func (k *TargetsCollection) GetAll() ([]*Target, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(targetTableName, all, true) if err != nil { return nil, err } var res []*Target for el := iter.Next(); el != nil; el = iter.Next() { t, ok := el.(*Target) if !ok { panic(unexpectedType) } res = append(res, &Target{Target: *t.DeepCopy()}) } txn.Commit() return res, nil } // GetAllByUpstreamID returns all targets referencing a Upstream // by its ID. func (k *TargetsCollection) GetAllByUpstreamID(id string) ([]*Target, error) { txn := k.db.Txn(false) return getAllByUpstreamID(txn, id) } deck-1.4.0/state/target_test.go000066400000000000000000000134651400603563700164330ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func targetsCollection() *TargetsCollection { return state().Targets } func TestTargetInsert(t *testing.T) { assert := assert.New(t) collection := targetsCollection() var t0 Target t0.Target.Target = kong.String("my-target") err := collection.Add(t0) assert.NotNil(err) t0.ID = kong.String("first") err = collection.Add(t0) assert.NotNil(err) var t1 Target t1.Target.Target = kong.String("my-target") t1.ID = kong.String("first") t1.Upstream = &kong.Upstream{ ID: kong.String("upstream1-id"), } err = collection.Add(t1) assert.Nil(err) var t2 Target t2.Target.Target = kong.String("my-target") t2.ID = kong.String("second") t2.Upstream = &kong.Upstream{ ID: kong.String("upstream1-id"), } err = collection.Add(t2) assert.NotNil(err) var t3 Target t3.Target.Target = kong.String("my-target") t3.ID = kong.String("third") t3.Upstream = &kong.Upstream{ Name: kong.String("upstream1-id"), } err = collection.Add(t3) assert.NotNil(err) } func TestTargetGetUpdate(t *testing.T) { assert := assert.New(t) collection := targetsCollection() var target Target target.Target.Target = kong.String("my-target") target.ID = kong.String("first") target.Upstream = &kong.Upstream{ ID: kong.String("upstream1-id"), } assert.NotNil(target.Upstream) err := collection.Add(target) assert.Nil(err) re, err := collection.Get("upstream1-id", "first") assert.Nil(err) assert.NotNil(re) assert.Equal("my-target", *re.Target.Target) re.ID = nil re.Upstream.ID = nil assert.NotNil(collection.Update(*re)) re.ID = kong.String("does-not-exist") assert.NotNil(collection.Update(*re)) re.ID = kong.String("first") assert.NotNil(collection.Update(*re)) re.Upstream.ID = kong.String("upstream1-id") assert.Nil(collection.Update(*re)) re.Upstream.ID = kong.String("upstream2-id") assert.Nil(collection.Update(*re)) } // Regression test // to ensure that the memory reference of the pointer returned by Get() // is different from the one stored in MemDB. func TestTargetGetMemoryReference(t *testing.T) { assert := assert.New(t) collection := targetsCollection() var target Target target.Target.Target = kong.String("my-target") target.ID = kong.String("first") target.Upstream = &kong.Upstream{ ID: kong.String("upstream1-id"), } err := collection.Add(target) assert.Nil(err) re, err := collection.Get("upstream1-id", "first") assert.Nil(err) assert.NotNil(re) assert.Equal("my-target", *re.Target.Target) re.Weight = kong.Int(1) re, err = collection.Get("upstream1-id", "my-target") assert.Nil(err) assert.NotNil(re) assert.Nil(re.Weight) } func TestTargetsInvalidType(t *testing.T) { assert := assert.New(t) collection := targetsCollection() var upstream Upstream upstream.Name = kong.String("my-upstream") upstream.ID = kong.String("first") txn := collection.db.Txn(true) err := txn.Insert(targetTableName, &upstream) assert.NotNil(err) txn.Abort() type badTarget struct { kong.Target Meta } target := badTarget{ Target: kong.Target{ ID: kong.String("id"), Target: kong.String("target"), Upstream: &kong.Upstream{ ID: kong.String("upstream-id"), }, }, } txn = collection.db.Txn(true) err = txn.Insert(targetTableName, &target) assert.Nil(err) txn.Commit() assert.Panics(func() { collection.Get("upstream-id", "id") }) assert.Panics(func() { collection.GetAll() }) } func TestTargetDelete(t *testing.T) { assert := assert.New(t) collection := targetsCollection() var target Target target.Target.Target = kong.String("my-target") target.ID = kong.String("first") target.Upstream = &kong.Upstream{ ID: kong.String("upstream1-id"), } err := collection.Add(target) assert.Nil(err) re, err := collection.Get("upstream1-id", "my-target") assert.Nil(err) assert.NotNil(re) err = collection.Delete("upstream1-id", *re.ID) assert.Nil(err) err = collection.Delete("upstream1-id", *re.ID) assert.NotNil(err) err = collection.Delete("", "first") assert.NotNil(err) err = collection.Delete("foo", "") assert.NotNil(err) } func TestTargetGetAll(t *testing.T) { assert := assert.New(t) collection := targetsCollection() var target Target target.Target.Target = kong.String("my-target1") target.ID = kong.String("first") target.Upstream = &kong.Upstream{ ID: kong.String("upstream1-id"), } err := collection.Add(target) assert.Nil(err) var target2 Target target2.Target.Target = kong.String("my-target2") target2.ID = kong.String("second") target2.Upstream = &kong.Upstream{ ID: kong.String("upstream1-id"), } err = collection.Add(target2) assert.Nil(err) targets, err := collection.GetAll() assert.Nil(err) assert.Equal(2, len(targets)) } func TestTargetGetAllByUpstreamName(t *testing.T) { assert := assert.New(t) collection := targetsCollection() targets := []*Target{ { Target: kong.Target{ ID: kong.String("target1-id"), Target: kong.String("target1-name"), Upstream: &kong.Upstream{ ID: kong.String("upstream1-id"), }, }, }, { Target: kong.Target{ ID: kong.String("target2-id"), Target: kong.String("target2-name"), Upstream: &kong.Upstream{ ID: kong.String("upstream1-id"), }, }, }, { Target: kong.Target{ ID: kong.String("target3-id"), Target: kong.String("target3-name"), Upstream: &kong.Upstream{ ID: kong.String("upstream2-id"), }, }, }, { Target: kong.Target{ ID: kong.String("target4-id"), Target: kong.String("target4-name"), Upstream: &kong.Upstream{ ID: kong.String("upstream2-id"), }, }, }, } for _, target := range targets { err := collection.Add(*target) assert.Nil(err) } targets, err := collection.GetAllByUpstreamID("upstream1-id") assert.Nil(err) assert.Equal(2, len(targets)) } deck-1.4.0/state/types.go000066400000000000000000000676401400603563700152560ustar00rootroot00000000000000package state import ( "reflect" "sort" "github.com/kong/go-kong/kong" ) // entity abstracts out common fields in a credentials. // TODO generalize for each and every entity. type entity interface { // ID of the cred. GetID() string // ID2 is the second endpoint key. GetID2() string // Consumer returns consumer ID associated with the cred. GetConsumer() string } // ConsoleString contains methods to be used to print // entity to console. type ConsoleString interface { // Console returns a string to uniquely identify an // entity in human-readable form. // It should have the ID or endpoint key along-with // foreign references if they exist. // It will be used to communicate to the human user // that this entity is undergoing some change. Console() string } // Meta contains additional information for an entity // type Meta struct { // Name *string `json:"name,omitempty" yaml:"name,omitempty"` // Global *bool `json:"global,omitempty" yaml:"global,omitempty"` // Kind *string `json:"type,omitempty" yaml:"type,omitempty"` // } // Meta stores metadata for any entity. type Meta struct { metaMap map[string]interface{} } func (m *Meta) initMeta() { if m.metaMap == nil { m.metaMap = make(map[string]interface{}) } } // AddMeta adds key->obj metadata. // It will override the old obj in key is already present. func (m *Meta) AddMeta(key string, obj interface{}) { m.initMeta() m.metaMap[key] = obj } // GetMeta returns the obj previously added using AddMeta(). // It returns nil if key is not present. func (m *Meta) GetMeta(key string) interface{} { m.initMeta() return m.metaMap[key] } // Service represents a service in Kong. // It adds some helper methods along with Meta to the original Service object. type Service struct { kong.Service `yaml:",inline"` Meta } // Identifier returns the endpoint key name or ID. func (s1 *Service) Identifier() string { if s1.Name != nil { return *s1.Name } return *s1.ID } // Console returns an entity's identity in a human // readable string. func (s1 *Service) Console() string { return s1.Identifier() } // Equal returns true if s1 and s2 are equal. func (s1 *Service) Equal(s2 *Service) bool { return s1.EqualWithOpts(s2, false, false) } // EqualWithOpts returns true if s1 and s2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (s1 *Service) EqualWithOpts(s2 *Service, ignoreID bool, ignoreTS bool) bool { s1Copy := s1.Service.DeepCopy() s2Copy := s2.Service.DeepCopy() // 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]) }) sort.Slice(s2Copy.Tags, func(i, j int) bool { return *(s2Copy.Tags[i]) < *(s2Copy.Tags[j]) }) if ignoreID { s1Copy.ID = nil s2Copy.ID = nil } if ignoreTS { s1Copy.CreatedAt = nil s2Copy.CreatedAt = nil s1Copy.UpdatedAt = nil s2Copy.UpdatedAt = nil } return reflect.DeepEqual(s1Copy, s2Copy) } // Route represents a route in Kong. // It adds some helper methods along with Meta to the original Route object. type Route struct { kong.Route `yaml:",inline"` Meta } // Identifier returns the endpoint key name or ID. func (r1 *Route) Identifier() string { if r1.Name != nil { return *r1.Name } return *r1.ID } // Console returns an entity's identity in a human // readable string. func (r1 *Route) Console() string { return r1.Identifier() } // Equal returns true if r1 and r2 are equal. // TODO add compare array without position func (r1 *Route) Equal(r2 *Route) bool { return r1.EqualWithOpts(r2, false, false, false) } // EqualWithOpts returns true if r1 and r2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (r1 *Route) EqualWithOpts(r2 *Route, ignoreID, ignoreTS, ignoreForeign bool) bool { r1Copy := r1.Route.DeepCopy() r2Copy := r2.Route.DeepCopy() 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]) }) if ignoreID { r1Copy.ID = nil r2Copy.ID = nil } if ignoreTS { r1Copy.CreatedAt = nil r2Copy.CreatedAt = nil r1Copy.UpdatedAt = nil r2Copy.UpdatedAt = nil } if ignoreForeign { r1Copy.Service = nil r2Copy.Service = nil } return reflect.DeepEqual(r1Copy, r2Copy) } // Upstream represents a upstream in Kong. // It adds some helper methods along with Meta to the original Upstream object. type Upstream struct { kong.Upstream `yaml:",inline"` Meta } // Identifier returns the endpoint key name or ID. func (u1 *Upstream) Identifier() string { if u1.Name != nil { return *u1.Name } return *u1.ID } // Console returns an entity's identity in a human // readable string. func (u1 *Upstream) Console() string { return u1.Identifier() } // Equal returns true if u1 and u2 are equal. func (u1 *Upstream) Equal(u2 *Upstream) bool { return u1.EqualWithOpts(u2, false, false) } // EqualWithOpts returns true if u1 and u2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (u1 *Upstream) EqualWithOpts(u2 *Upstream, ignoreID bool, ignoreTS bool) bool { u1Copy := u1.Upstream.DeepCopy() u2Copy := u2.Upstream.DeepCopy() 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]) }) if ignoreID { u1Copy.ID = nil u2Copy.ID = nil } if ignoreTS { u1Copy.CreatedAt = nil u2Copy.CreatedAt = nil } return reflect.DeepEqual(u1Copy, u2Copy) } // Target represents a Target in Kong. // It adds some helper methods along with Meta to the original Target object. type Target struct { kong.Target `yaml:",inline"` Meta } // Identifier returns the endpoint key name or ID. func (t1 *Target) Identifier() string { if t1.Target.Target != nil { return *t1.Target.Target } return *t1.ID } // Console returns an entity's identity in a human // readable string. func (t1 *Target) Console() string { res := t1.Identifier() if t1.Upstream != nil { if t1.Upstream.ID != nil { res = res + " for upstream " + *t1.Upstream.ID } if t1.Upstream.Name != nil { res = res + " for upstream " + *t1.Upstream.Name } } return res } // Equal returns true if t1 and t2 are equal. // TODO add compare array without position func (t1 *Target) Equal(t2 *Target) bool { return t1.EqualWithOpts(t2, false, false, false) } // EqualWithOpts returns true if t1 and t2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (t1 *Target) EqualWithOpts(t2 *Target, ignoreID, ignoreTS, ignoreForeign bool) bool { t1Copy := t1.Target.DeepCopy() t2Copy := t2.Target.DeepCopy() 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]) }) if ignoreID { t1Copy.ID = nil t2Copy.ID = nil } if ignoreTS { t1Copy.CreatedAt = nil t2Copy.CreatedAt = nil } if ignoreForeign { t1Copy.Upstream = nil t2Copy.Upstream = nil } return reflect.DeepEqual(t1Copy, t2Copy) } // Certificate represents a upstream in Kong. // It adds some helper methods along with Meta to the // original Certificate object. type Certificate struct { kong.Certificate `yaml:",inline"` Meta } // Identifier returns the endpoint key name or ID. func (c1 *Certificate) Identifier() string { if c1.ID != nil { return *c1.ID } return *c1.Cert } // Console returns an entity's identity in a human // readable string. func (c1 *Certificate) Console() string { return c1.Identifier() } // Equal returns true if c1 and c2 are equal. func (c1 *Certificate) Equal(c2 *Certificate) bool { return c1.EqualWithOpts(c2, false, false) } // EqualWithOpts returns true if c1 and c2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (c1 *Certificate) EqualWithOpts(c2 *Certificate, ignoreID bool, ignoreTS bool) bool { c1Copy := c1.Certificate.DeepCopy() c2Copy := c2.Certificate.DeepCopy() 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]) }) if ignoreID { c1Copy.ID = nil c2Copy.ID = nil } if ignoreTS { c1Copy.CreatedAt = nil c2Copy.CreatedAt = nil } return reflect.DeepEqual(c1Copy, c2Copy) } // SNI represents a SNI in Kong. // It adds some helper methods along with Meta to the original SNI object. type SNI struct { kong.SNI `yaml:",inline"` Meta } // Equal returns true if s1 and s2 are equal. // TODO add compare array without position func (s1 *SNI) Equal(s2 *SNI) bool { return s1.EqualWithOpts(s2, false, false, false) } // Identifier returns the endpoint key name or ID. func (s1 *SNI) Identifier() string { if s1.Name != nil { return *s1.Name } return *s1.ID } // Console returns an entity's identity in a human // readable string. func (s1 *SNI) Console() string { return s1.Identifier() } // EqualWithOpts returns true if s1 and s2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (s1 *SNI) EqualWithOpts(s2 *SNI, ignoreID, ignoreTS, ignoreForeign bool) bool { s1Copy := s1.SNI.DeepCopy() s2Copy := s2.SNI.DeepCopy() 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]) }) if ignoreID { s1Copy.ID = nil s2Copy.ID = nil } if ignoreTS { s1Copy.CreatedAt = nil s2Copy.CreatedAt = nil } if ignoreForeign { s1Copy.Certificate = nil s2Copy.Certificate = nil } return reflect.DeepEqual(s1Copy, s2Copy) } // Plugin represents a route in Kong. // It adds some helper methods along with Meta to the original Plugin object. type Plugin struct { kong.Plugin `yaml:",inline"` Meta } // Identifier returns the endpoint key name or ID. func (p1 *Plugin) Identifier() string { if p1.Name != nil { return *p1.Name } return *p1.ID } // Console returns an entity's identity in a human // readable string. func (p1 *Plugin) Console() string { res := *p1.Name + " " if p1.Service == nil && p1.Route == nil && p1.Consumer == nil { return res + "(global)" } associations := []string{} if p1.Service != nil { associations = append(associations, "service "+*p1.Service.ID) } if p1.Route != nil { associations = append(associations, "route "+*p1.Route.ID) } if p1.Consumer != nil { associations = append(associations, "consumer "+*p1.Consumer.ID) } if len(associations) > 0 { res += "for " } for i := 0; i < len(associations); i++ { res += associations[i] if i < len(associations)-1 { res += " and " } } return res } // Equal returns true if r1 and r2 are equal. // TODO add compare array without position func (p1 *Plugin) Equal(p2 *Plugin) bool { return p1.EqualWithOpts(p2, false, false, false) } // EqualWithOpts returns true if p1 and p2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (p1 *Plugin) EqualWithOpts(p2 *Plugin, ignoreID, ignoreTS, ignoreForeign bool) bool { p1Copy := p1.Plugin.DeepCopy() p2Copy := p2.Plugin.DeepCopy() 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]) }) if ignoreID { p1Copy.ID = nil p2Copy.ID = nil } if ignoreTS { p1Copy.CreatedAt = nil p2Copy.CreatedAt = nil } if ignoreForeign { p1Copy.Service = nil p1Copy.Route = nil p1Copy.Consumer = nil p2Copy.Service = nil p2Copy.Route = nil p2Copy.Consumer = nil } return reflect.DeepEqual(p1Copy, p2Copy) } // Consumer represents a consumer in Kong. // It adds some helper methods along with Meta to the original Consumer object. type Consumer struct { kong.Consumer `yaml:",inline"` Meta } // Identifier returns the endpoint key name or ID. func (c1 *Consumer) Identifier() string { if c1.Username != nil { return *c1.Username } return *c1.ID } // Console returns an entity's identity in a human // readable string. func (c1 *Consumer) Console() string { return c1.Identifier() } // Equal returns true if c1 and c2 are equal. func (c1 *Consumer) Equal(c2 *Consumer) bool { return c1.EqualWithOpts(c2, false, false) } // EqualWithOpts returns true if c1 and c2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (c1 *Consumer) EqualWithOpts(c2 *Consumer, ignoreID bool, ignoreTS bool) bool { c1Copy := c1.Consumer.DeepCopy() c2Copy := c2.Consumer.DeepCopy() 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]) }) if ignoreID { c1Copy.ID = nil c2Copy.ID = nil } if ignoreTS { c1Copy.CreatedAt = nil c2Copy.CreatedAt = nil } return reflect.DeepEqual(c1Copy, c2Copy) } func forConsumerString(c *kong.Consumer) string { if c != nil { if c.Username != nil { return " for consumer " + *c.Username } if c.ID != nil { return " for consumer " + *c.ID } } return "" } // KeyAuth represents a key-auth credential in Kong. // It adds some helper methods along with Meta to the original KeyAuth object. type KeyAuth struct { kong.KeyAuth `yaml:",inline"` Meta } // stripKey returns the last 5 characters of key. // If key is less than or equal to 5 characters, then the key is returned as is. func stripKey(key string) string { const keyIdentifierLength = 5 if len(key) <= keyIdentifierLength { return key } return key[len(key)-keyIdentifierLength:] } // Console returns an entity's identity in a human // readable string. func (k1 *KeyAuth) Console() string { return stripKey(*k1.Key) + forConsumerString(k1.Consumer) } // Equal returns true if k1 and k2 are equal. func (k1 *KeyAuth) Equal(k2 *KeyAuth) bool { return k1.EqualWithOpts(k2, false, false, false) } // EqualWithOpts returns true if k1 and k2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (k1 *KeyAuth) EqualWithOpts(k2 *KeyAuth, ignoreID, ignoreTS, ignoreForeign bool) bool { k1Copy := k1.KeyAuth.DeepCopy() k2Copy := k2.KeyAuth.DeepCopy() 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]) }) if ignoreID { k1Copy.ID = nil k2Copy.ID = nil } if ignoreTS { k1Copy.CreatedAt = nil k2Copy.CreatedAt = nil } if ignoreForeign { k1Copy.Consumer = nil k2Copy.Consumer = nil } return reflect.DeepEqual(k1Copy, k2Copy) } // GetID returns ID. // If ID is empty, it returns an empty string. func (k1 *KeyAuth) GetID() string { if k1.ID == nil { return "" } return *k1.ID } // GetID2 returns the endpoint key of the entity, // the Key field for KeyAuth. func (k1 *KeyAuth) GetID2() string { if k1.Key == nil { return "" } return *k1.Key } // GetConsumer returns the credential's Consumer's ID. // If Consumer's ID is empty, it returns an empty string. func (k1 *KeyAuth) GetConsumer() string { if k1.Consumer == nil || k1.Consumer.ID == nil { return "" } return *k1.Consumer.ID } // HMACAuth represents a key-auth credential in Kong. // It adds some helper methods along with Meta to the original HMACAuth object. type HMACAuth struct { kong.HMACAuth `yaml:",inline"` Meta } // Console returns an entity's identity in a human // readable string. func (h1 *HMACAuth) Console() string { return *h1.Username + forConsumerString(h1.Consumer) } // Equal returns true if h1 and h2 are equal. func (h1 *HMACAuth) Equal(h2 *HMACAuth) bool { return h1.EqualWithOpts(h2, false, false, false) } // EqualWithOpts returns true if h1 and h2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (h1 *HMACAuth) EqualWithOpts(h2 *HMACAuth, ignoreID, ignoreTS, ignoreForeign bool) bool { h1Copy := h1.HMACAuth.DeepCopy() h2Copy := h2.HMACAuth.DeepCopy() 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]) }) if ignoreID { h1Copy.ID = nil h2Copy.ID = nil } if ignoreTS { h1Copy.CreatedAt = nil h2Copy.CreatedAt = nil } if ignoreForeign { h1Copy.Consumer = nil h2Copy.Consumer = nil } return reflect.DeepEqual(h1Copy, h2Copy) } // GetID returns ID. // If ID is empty, it returns an empty string. func (h1 *HMACAuth) GetID() string { if h1.ID == nil { return "" } return *h1.ID } // GetID2 returns the endpoint key of the entity, // the Username field for HMACAuth. func (h1 *HMACAuth) GetID2() string { if h1.Username == nil { return "" } return *h1.Username } // GetConsumer returns the credential's Consumer's ID. // If Consumer's ID is empty, it returns an empty string. func (h1 *HMACAuth) GetConsumer() string { if h1.Consumer == nil || h1.Consumer.ID == nil { return "" } return *h1.Consumer.ID } // JWTAuth represents a jwt credential in Kong. // It adds some helper methods along with Meta to the original JWTAuth object. type JWTAuth struct { kong.JWTAuth `yaml:",inline"` Meta } // Console returns an entity's identity in a human // readable string. func (j1 *JWTAuth) Console() string { return *j1.Key + forConsumerString(j1.Consumer) } // Equal returns true if j1 and j2 are equal. func (j1 *JWTAuth) Equal(j2 *JWTAuth) bool { return j1.EqualWithOpts(j2, false, false, false) } // EqualWithOpts returns true if j1 and j2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (j1 *JWTAuth) EqualWithOpts(j2 *JWTAuth, ignoreID, ignoreTS, ignoreForeign bool) bool { j1Copy := j1.JWTAuth.DeepCopy() j2Copy := j2.JWTAuth.DeepCopy() 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]) }) if ignoreID { j1Copy.ID = nil j2Copy.ID = nil } if ignoreTS { j1Copy.CreatedAt = nil j2Copy.CreatedAt = nil } if ignoreForeign { j1Copy.Consumer = nil j2Copy.Consumer = nil } return reflect.DeepEqual(j1Copy, j2Copy) } // GetID returns ID. // If ID is empty, it returns an empty string. func (j1 *JWTAuth) GetID() string { if j1.ID == nil { return "" } return *j1.ID } // GetID2 returns the endpoint key of the entity, // the Key field for JWTAuth. func (j1 *JWTAuth) GetID2() string { if j1.Key == nil { return "" } return *j1.Key } // GetConsumer returns the credential's Consumer's ID. // If Consumer's ID is empty, it returns an empty string. func (j1 *JWTAuth) GetConsumer() string { if j1.Consumer == nil || j1.Consumer.ID == nil { return "" } return *j1.Consumer.ID } // BasicAuth represents a basic-auth credential in Kong. // It adds some helper methods along with Meta to the original BasicAuth object. type BasicAuth struct { kong.BasicAuth `yaml:",inline"` Meta } // Console returns an entity's identity in a human // readable string. func (b1 *BasicAuth) Console() string { return *b1.Username + forConsumerString(b1.Consumer) } // Equal returns true if b1 and b2 are equal. func (b1 *BasicAuth) Equal(b2 *BasicAuth) bool { return b1.EqualWithOpts(b2, false, false, false, false) } // EqualWithOpts returns true if j1 and j2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (b1 *BasicAuth) EqualWithOpts(b2 *BasicAuth, ignoreID, ignoreTS, ignorePassword, ignoreForeign bool) bool { b1Copy := b1.BasicAuth.DeepCopy() b2Copy := b2.BasicAuth.DeepCopy() 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]) }) if ignoreID { b1Copy.ID = nil b2Copy.ID = nil } if ignoreTS { b1Copy.CreatedAt = nil b2Copy.CreatedAt = nil } if ignorePassword { b1Copy.Password = nil b2Copy.Password = nil } if ignoreForeign { b1Copy.Consumer = nil b2Copy.Consumer = nil } return reflect.DeepEqual(b1Copy, b2Copy) } // GetID returns ID. // If ID is empty, it returns an empty string. func (b1 *BasicAuth) GetID() string { if b1.ID == nil { return "" } return *b1.ID } // GetID2 returns the endpoint key of the entity, // the Username field for BasicAuth. func (b1 *BasicAuth) GetID2() string { if b1.Username == nil { return "" } return *b1.Username } // GetConsumer returns the credential's Consumer's ID. // If Consumer's ID is empty, it returns an empty string. func (b1 *BasicAuth) GetConsumer() string { if b1.Consumer == nil || b1.Consumer.ID == nil { return "" } return *b1.Consumer.ID } // ACLGroup represents an ACL group for a consumer in Kong. // It adds some helper methods along with Meta to the original ACLGroup object. type ACLGroup struct { kong.ACLGroup `yaml:",inline"` Meta } // Console returns an entity's identity in a human // readable string. func (b1 *ACLGroup) Console() string { return *b1.Group + forConsumerString(b1.Consumer) } // Equal returns true if b1 and b2 are equal. func (b1 *ACLGroup) Equal(b2 *ACLGroup) bool { return b1.EqualWithOpts(b2, false, false, false) } // EqualWithOpts returns true if j1 and j2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (b1 *ACLGroup) EqualWithOpts(b2 *ACLGroup, ignoreID, ignoreTS, ignoreForeign bool) bool { b1Copy := b1.ACLGroup.DeepCopy() b2Copy := b2.ACLGroup.DeepCopy() 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]) }) if ignoreID { b1Copy.ID = nil b2Copy.ID = nil } if ignoreTS { b1Copy.CreatedAt = nil b2Copy.CreatedAt = nil } if ignoreForeign { b1Copy.Consumer = nil b2Copy.Consumer = nil } return reflect.DeepEqual(b1Copy, b2Copy) } // CACertificate represents a CACertificate in Kong. // It adds some helper methods along with Meta to the // original CACertificate object. type CACertificate struct { kong.CACertificate `yaml:",inline"` Meta } // Identifier returns the endpoint key name or ID. func (c1 *CACertificate) Identifier() string { if c1.ID != nil { return *c1.ID } return *c1.Cert } // Console returns an entity's identity in a human // readable string. func (c1 *CACertificate) Console() string { return c1.Identifier() } // Equal returns true if c1 and c2 are equal. func (c1 *CACertificate) Equal(c2 *CACertificate) bool { return c1.EqualWithOpts(c2, false, false) } // EqualWithOpts returns true if c1 and c2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (c1 *CACertificate) EqualWithOpts(c2 *CACertificate, ignoreID bool, ignoreTS bool) bool { c1Copy := c1.CACertificate.DeepCopy() c2Copy := c2.CACertificate.DeepCopy() 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]) }) if ignoreID { c1Copy.ID = nil c2Copy.ID = nil } if ignoreTS { c1Copy.CreatedAt = nil c2Copy.CreatedAt = nil } return reflect.DeepEqual(c1Copy, c2Copy) } // Oauth2Credential represents an Oauth2 credential in Kong. // It adds some helper methods along with Meta to the original Oauth2Credential object. type Oauth2Credential struct { kong.Oauth2Credential `yaml:",inline"` Meta } // Console returns an entity's identity in a human // readable string. func (k1 *Oauth2Credential) Console() string { return *k1.Name + forConsumerString(k1.Consumer) } // Equal returns true if k1 and k2 are equal. func (k1 *Oauth2Credential) Equal(k2 *Oauth2Credential) bool { return k1.EqualWithOpts(k2, false, false, false) } // EqualWithOpts returns true if k1 and k2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (k1 *Oauth2Credential) EqualWithOpts(k2 *Oauth2Credential, ignoreID, ignoreTS, ignoreForeign bool) bool { k1Copy := k1.Oauth2Credential.DeepCopy() k2Copy := k2.Oauth2Credential.DeepCopy() 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]) }) if ignoreID { k1Copy.ID = nil k2Copy.ID = nil } if ignoreTS { k1Copy.CreatedAt = nil k2Copy.CreatedAt = nil } if ignoreForeign { k1Copy.Consumer = nil k2Copy.Consumer = nil } return reflect.DeepEqual(k1Copy, k2Copy) } // GetID returns ID. // If ID is empty, it returns an empty string. func (k1 *Oauth2Credential) GetID() string { if k1.ID == nil { return "" } return *k1.ID } // GetID2 returns the endpoint key of the entity, // the ClientID field for Oauth2Credential. func (k1 *Oauth2Credential) GetID2() string { if k1.ClientID == nil { return "" } return *k1.ClientID } // GetConsumer returns the credential's Consumer's ID. // If Consumer's ID is empty, it returns an empty string. func (k1 *Oauth2Credential) GetConsumer() string { if k1.Consumer == nil || k1.Consumer.ID == nil { return "" } return *k1.Consumer.ID } // MTLSAuth represents an mtls-auth credential in Kong. // It adds some helper methods along with Meta to the original MTLSAuth object. type MTLSAuth struct { kong.MTLSAuth `yaml:",inline"` Meta } // Console returns an entity's identity in a human // readable string. func (b1 *MTLSAuth) Console() string { return *b1.SubjectName + forConsumerString(b1.Consumer) } // Equal returns true if b1 and b2 are equal. func (b1 *MTLSAuth) Equal(b2 *MTLSAuth) bool { return b1.EqualWithOpts(b2, false, false, false) } // EqualWithOpts returns true if j1 and j2 are equal. // If ignoreID is set to true, IDs will be ignored while comparison. // If ignoreTS is set to true, timestamp fields will be ignored. func (b1 *MTLSAuth) EqualWithOpts(b2 *MTLSAuth, ignoreID, ignoreTS, ignoreForeign bool) bool { b1Copy := b1.MTLSAuth.DeepCopy() b2Copy := b2.MTLSAuth.DeepCopy() 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]) }) if ignoreID { b1Copy.ID = nil b2Copy.ID = nil } if ignoreTS { b1Copy.CreatedAt = nil b2Copy.CreatedAt = nil } if ignoreForeign { b1Copy.Consumer = nil b2Copy.Consumer = nil } return reflect.DeepEqual(b1Copy, b2Copy) } // GetID returns ID. // If ID is empty, it returns an empty string. func (b1 *MTLSAuth) GetID() string { if b1.ID == nil { return "" } return *b1.ID } // GetID2 returns the endpoint key of the entity, // BUT NO SUCH THING EXISTS 😱 // TODO: this is kind of a pointless clone of GetID for MTLSAuth. the mtls-auth // entity cannot be referenced by anything other than its ID (it has no unique // fields), but the entity interface requires this function. this duplication // doesn't appear to be harmful, but it's weird. func (b1 *MTLSAuth) GetID2() string { return (*b1).GetID() } func (b1 *MTLSAuth) GetConsumer() string { if b1.Consumer == nil || b1.Consumer.ID == nil { return "" } return *b1.Consumer.ID } deck-1.4.0/state/types_test.go000066400000000000000000000341441400603563700163060ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) // getTags returns a slice of test tags. If reversed is true, the tags are backwards! // backwards tag slices are useful for confirming that our equality checks ignore tag order func getTags(reversed bool) []*string { fooString := "foo" barString := "bar" if reversed { return []*string{&barString, &fooString} } return []*string{&fooString, &barString} } func TestMeta(t *testing.T) { assert := assert.New(t) var m Meta m.AddMeta("foo", "bar") r := m.GetMeta("foo") res, ok := r.(string) assert.True(ok) assert.Equal("bar", res) // assert.Equal(reflect.TypeOf(r).String(), "string") s := "string-pointer" m.AddMeta("baz", &s) r = m.GetMeta("baz") res2, ok := r.(*string) assert.True(ok) assert.Equal("string-pointer", *res2) // can retrieve a previous value r = m.GetMeta("foo") res, ok = r.(string) assert.True(ok) assert.Equal("bar", res) } func TestServiceEqual(t *testing.T) { assert := assert.New(t) var s1, s2 Service s1.ID = kong.String("foo") s1.Name = kong.String("bar") s2.ID = kong.String("foo") s2.Name = kong.String("baz") assert.False(s1.Equal(&s2)) assert.False(s1.EqualWithOpts(&s2, false, false)) s2.Name = kong.String("bar") assert.True(s1.Equal(&s2)) assert.True(s1.EqualWithOpts(&s2, false, false)) s1.Tags = getTags(true) s2.Tags = getTags(false) assert.True(s1.EqualWithOpts(&s2, false, false)) s1.ID = kong.String("fuu") assert.False(s1.EqualWithOpts(&s2, false, false)) assert.True(s1.EqualWithOpts(&s2, true, false)) s2.CreatedAt = kong.Int(1) s1.UpdatedAt = kong.Int(2) assert.False(s1.EqualWithOpts(&s2, false, false)) assert.False(s1.EqualWithOpts(&s2, false, true)) } func TestRouteEqual(t *testing.T) { assert := assert.New(t) var r1, r2 Route r1.ID = kong.String("foo") r1.Name = kong.String("bar") r2.ID = kong.String("foo") r2.Name = kong.String("baz") assert.False(r1.Equal(&r2)) assert.False(r1.EqualWithOpts(&r2, false, false, false)) r2.Name = kong.String("bar") assert.True(r1.Equal(&r2)) assert.True(r1.EqualWithOpts(&r2, false, false, false)) r1.Tags = getTags(true) r2.Tags = getTags(false) assert.True(r1.EqualWithOpts(&r2, false, false, false)) r1.ID = kong.String("fuu") assert.False(r1.EqualWithOpts(&r2, false, false, false)) assert.True(r1.EqualWithOpts(&r2, true, false, false)) r2.CreatedAt = kong.Int(1) r1.UpdatedAt = kong.Int(2) assert.False(r1.EqualWithOpts(&r2, false, false, false)) assert.False(r1.EqualWithOpts(&r2, false, true, false)) assert.True(r1.EqualWithOpts(&r2, true, true, false)) r1.Hosts = kong.StringSlice("demo1.example.com", "demo2.example.com") // order matters r2.Hosts = kong.StringSlice("demo2.example.com", "demo1.example.com") assert.False(r1.EqualWithOpts(&r2, true, true, false)) r2.Hosts = kong.StringSlice("demo1.example.com", "demo2.example.com") assert.True(r1.EqualWithOpts(&r2, true, true, false)) r1.Service = &kong.Service{ID: kong.String("1")} r2.Service = &kong.Service{ID: kong.String("2")} assert.False(r1.EqualWithOpts(&r2, true, true, false)) assert.True(r1.EqualWithOpts(&r2, true, true, true)) r1.Service = &kong.Service{ID: kong.String("2")} assert.True(r1.EqualWithOpts(&r2, true, true, false)) } func TestUpstreamEqual(t *testing.T) { assert := assert.New(t) var u1, u2 Upstream u1.ID = kong.String("foo") u1.Name = kong.String("bar") u2.ID = kong.String("foo") u2.Name = kong.String("baz") assert.False(u1.Equal(&u2)) assert.False(u1.EqualWithOpts(&u2, false, false)) u2.Name = kong.String("bar") assert.True(u1.Equal(&u2)) assert.True(u1.EqualWithOpts(&u2, false, false)) u1.Tags = getTags(true) u2.Tags = getTags(false) assert.True(u1.EqualWithOpts(&u2, false, false)) u1.ID = kong.String("fuu") assert.False(u1.EqualWithOpts(&u2, false, false)) assert.True(u1.EqualWithOpts(&u2, true, false)) var timestamp int64 = 1 u2.CreatedAt = ×tamp assert.False(u1.EqualWithOpts(&u2, false, false)) assert.False(u1.EqualWithOpts(&u2, false, true)) } func TestTargetEqual(t *testing.T) { assert := assert.New(t) var t1, t2 Target t1.ID = kong.String("foo") t1.Target.Target = kong.String("bar") t2.ID = kong.String("foo") t2.Target.Target = kong.String("baz") assert.False(t1.Equal(&t2)) assert.False(t1.EqualWithOpts(&t2, false, false, false)) t2.Target.Target = kong.String("bar") assert.True(t1.Equal(&t2)) assert.True(t1.EqualWithOpts(&t2, false, false, false)) t1.Tags = getTags(true) t2.Tags = getTags(false) assert.True(t1.EqualWithOpts(&t2, false, false, false)) t1.ID = kong.String("fuu") assert.False(t1.EqualWithOpts(&t2, false, false, false)) assert.True(t1.EqualWithOpts(&t2, true, false, false)) var timestamp float64 = 1 t2.CreatedAt = ×tamp assert.False(t1.EqualWithOpts(&t2, false, false, false)) assert.False(t1.EqualWithOpts(&t2, false, true, false)) t1.Upstream = &kong.Upstream{ID: kong.String("1")} t2.Upstream = &kong.Upstream{ID: kong.String("2")} assert.False(t1.EqualWithOpts(&t2, true, true, false)) assert.True(t1.EqualWithOpts(&t2, true, true, true)) t1.Upstream = &kong.Upstream{ID: kong.String("2")} assert.True(t1.EqualWithOpts(&t2, true, true, false)) } func TestCertificateEqual(t *testing.T) { assert := assert.New(t) var c1, c2 Certificate c1.ID = kong.String("foo") c1.Cert = kong.String("certfoo") c1.Key = kong.String("keyfoo") c2.ID = kong.String("foo") c2.Cert = kong.String("certfoo") c2.Key = kong.String("keyfoo-unequal") assert.False(c1.Equal(&c2)) assert.False(c1.EqualWithOpts(&c2, false, false)) c2.Key = kong.String("keyfoo") assert.True(c1.Equal(&c2)) assert.True(c1.EqualWithOpts(&c2, false, false)) c1.Tags = getTags(true) c2.Tags = getTags(false) assert.True(c1.EqualWithOpts(&c2, false, false)) c1.ID = kong.String("fuu") assert.False(c1.EqualWithOpts(&c2, false, false)) assert.True(c1.EqualWithOpts(&c2, true, false)) var timestamp int64 = 1 c2.CreatedAt = ×tamp assert.False(c1.EqualWithOpts(&c2, false, false)) assert.False(c1.EqualWithOpts(&c2, false, true)) } func TestSNIEqual(t *testing.T) { assert := assert.New(t) var s1, s2 SNI s1.ID = kong.String("foo") s1.Name = kong.String("bar") s2.ID = kong.String("foo") s2.Name = kong.String("baz") assert.False(s1.Equal(&s2)) assert.False(s1.EqualWithOpts(&s2, false, false, false)) s2.Name = kong.String("bar") assert.True(s1.Equal(&s2)) assert.True(s1.EqualWithOpts(&s2, false, false, false)) s1.Tags = getTags(true) s2.Tags = getTags(false) assert.True(s1.EqualWithOpts(&s2, false, false, false)) s1.ID = kong.String("fuu") assert.False(s1.EqualWithOpts(&s2, false, false, false)) assert.True(s1.EqualWithOpts(&s2, true, false, false)) var timestamp int64 = 1 s2.CreatedAt = ×tamp assert.False(s1.EqualWithOpts(&s2, false, false, false)) assert.False(s1.EqualWithOpts(&s2, false, true, false)) s1.Certificate = &kong.Certificate{ID: kong.String("1")} s2.Certificate = &kong.Certificate{ID: kong.String("2")} assert.False(s1.EqualWithOpts(&s2, true, true, false)) assert.True(s1.EqualWithOpts(&s2, true, true, true)) s1.Certificate = &kong.Certificate{ID: kong.String("2")} assert.True(s1.EqualWithOpts(&s2, true, true, false)) } func TestPluginEqual(t *testing.T) { assert := assert.New(t) var p1, p2 Plugin p1.ID = kong.String("foo") p1.Name = kong.String("bar") p2.ID = kong.String("foo") p2.Name = kong.String("baz") assert.False(p1.Equal(&p2)) assert.False(p1.EqualWithOpts(&p2, false, false, false)) p2.Name = kong.String("bar") assert.True(p1.Equal(&p2)) assert.True(p1.EqualWithOpts(&p2, false, false, false)) p1.Tags = getTags(true) p2.Tags = getTags(false) assert.True(p1.EqualWithOpts(&p2, false, false, false)) p1.ID = kong.String("fuu") assert.False(p1.EqualWithOpts(&p2, false, false, false)) assert.True(p1.EqualWithOpts(&p2, true, false, false)) timestamp := 1 p2.CreatedAt = ×tamp assert.False(p1.EqualWithOpts(&p2, false, false, false)) assert.False(p1.EqualWithOpts(&p2, false, true, false)) p1.Service = &kong.Service{ID: kong.String("1")} p2.Service = &kong.Service{ID: kong.String("2")} assert.False(p1.EqualWithOpts(&p2, true, true, false)) assert.True(p1.EqualWithOpts(&p2, true, true, true)) p1.Service = &kong.Service{ID: kong.String("2")} assert.True(p1.EqualWithOpts(&p2, true, true, false)) } func TestConsumerEqual(t *testing.T) { assert := assert.New(t) var c1, c2 Consumer c1.ID = kong.String("foo") c1.Username = kong.String("bar") c2.ID = kong.String("foo") c2.Username = kong.String("baz") assert.False(c1.Equal(&c2)) assert.False(c1.EqualWithOpts(&c2, false, false)) c2.Username = kong.String("bar") assert.True(c1.Equal(&c2)) assert.True(c1.EqualWithOpts(&c2, false, false)) c1.Tags = getTags(true) c2.Tags = getTags(false) assert.True(c1.EqualWithOpts(&c2, false, false)) c1.ID = kong.String("fuu") assert.False(c1.EqualWithOpts(&c2, false, false)) assert.True(c1.EqualWithOpts(&c2, true, false)) var a int64 = 1 c2.CreatedAt = &a assert.False(c1.EqualWithOpts(&c2, false, false)) assert.False(c1.EqualWithOpts(&c2, false, true)) } func TestKeyAuthEqual(t *testing.T) { assert := assert.New(t) var k1, k2 KeyAuth k1.ID = kong.String("foo") k1.Key = kong.String("bar") k2.ID = kong.String("foo") k2.Key = kong.String("baz") assert.False(k1.Equal(&k2)) assert.False(k1.EqualWithOpts(&k2, false, false, false)) k2.Key = kong.String("bar") assert.True(k1.Equal(&k2)) assert.True(k1.EqualWithOpts(&k2, false, false, false)) k1.Tags = getTags(true) k2.Tags = getTags(false) assert.True(k1.EqualWithOpts(&k2, false, false, false)) k1.ID = kong.String("fuu") assert.False(k1.EqualWithOpts(&k2, false, false, false)) assert.True(k1.EqualWithOpts(&k2, true, false, false)) k2.CreatedAt = kong.Int(1) assert.False(k1.EqualWithOpts(&k2, false, false, false)) assert.False(k1.EqualWithOpts(&k2, false, true, false)) k2.Consumer = &kong.Consumer{Username: kong.String("u1")} assert.False(k1.EqualWithOpts(&k2, false, true, false)) assert.False(k1.EqualWithOpts(&k2, false, false, true)) } func TestHMACAuthEqual(t *testing.T) { assert := assert.New(t) var k1, k2 HMACAuth k1.ID = kong.String("foo") k1.Username = kong.String("bar") k2.ID = kong.String("foo") k2.Username = kong.String("baz") assert.False(k1.Equal(&k2)) assert.False(k1.EqualWithOpts(&k2, false, false, false)) k2.Username = kong.String("bar") assert.True(k1.Equal(&k2)) assert.True(k1.EqualWithOpts(&k2, false, false, false)) k1.Tags = getTags(true) k2.Tags = getTags(false) assert.True(k1.EqualWithOpts(&k2, false, false, false)) k1.ID = kong.String("fuu") assert.False(k1.EqualWithOpts(&k2, false, false, false)) assert.True(k1.EqualWithOpts(&k2, true, false, false)) k2.CreatedAt = kong.Int(1) assert.False(k1.EqualWithOpts(&k2, false, false, false)) assert.False(k1.EqualWithOpts(&k2, false, true, false)) k2.Consumer = &kong.Consumer{Username: kong.String("u1")} assert.False(k1.EqualWithOpts(&k2, false, true, false)) assert.False(k1.EqualWithOpts(&k2, false, false, true)) } func TestJWTAuthEqual(t *testing.T) { assert := assert.New(t) var k1, k2 JWTAuth k1.ID = kong.String("foo") k1.Key = kong.String("bar") k2.ID = kong.String("foo") k2.Key = kong.String("baz") assert.False(k1.Equal(&k2)) assert.False(k1.EqualWithOpts(&k2, false, false, false)) k2.Key = kong.String("bar") assert.True(k1.Equal(&k2)) assert.True(k1.EqualWithOpts(&k2, false, false, false)) k1.Tags = getTags(true) k2.Tags = getTags(false) assert.True(k1.EqualWithOpts(&k2, false, false, false)) k1.ID = kong.String("fuu") assert.False(k1.EqualWithOpts(&k2, false, false, false)) assert.True(k1.EqualWithOpts(&k2, true, false, false)) k2.CreatedAt = kong.Int(1) assert.False(k1.EqualWithOpts(&k2, false, false, false)) assert.False(k1.EqualWithOpts(&k2, false, true, false)) k2.Consumer = &kong.Consumer{Username: kong.String("u1")} assert.False(k1.EqualWithOpts(&k2, false, true, false)) assert.False(k1.EqualWithOpts(&k2, false, false, true)) } func TestBasicAuthEqual(t *testing.T) { assert := assert.New(t) var k1, k2 BasicAuth k1.ID = kong.String("foo") k1.Password = kong.String("bar") k2.ID = kong.String("foo") k2.Password = kong.String("baz") assert.False(k1.Equal(&k2)) assert.False(k1.EqualWithOpts(&k2, false, false, false, false)) k2.Password = kong.String("bar") assert.True(k1.Equal(&k2)) assert.True(k1.EqualWithOpts(&k2, false, false, false, false)) assert.True(k1.EqualWithOpts(&k2, false, false, false, true)) k1.Tags = getTags(true) k2.Tags = getTags(false) assert.True(k1.EqualWithOpts(&k2, false, false, false, false)) k1.ID = kong.String("fuu") assert.False(k1.EqualWithOpts(&k2, false, false, false, false)) assert.True(k1.EqualWithOpts(&k2, true, false, false, false)) k2.CreatedAt = kong.Int(1) assert.False(k1.EqualWithOpts(&k2, false, false, false, false)) assert.False(k1.EqualWithOpts(&k2, false, true, false, false)) k2.Consumer = &kong.Consumer{Username: kong.String("u1")} assert.False(k1.EqualWithOpts(&k2, false, true, false, false)) assert.False(k1.EqualWithOpts(&k2, false, false, true, false)) } func TestACLGroupEqual(t *testing.T) { assert := assert.New(t) var k1, k2 ACLGroup k1.ID = kong.String("foo") k1.Group = kong.String("bar") k2.ID = kong.String("foo") k2.Group = kong.String("baz") assert.False(k1.Equal(&k2)) assert.False(k1.EqualWithOpts(&k2, false, false, false)) k2.Group = kong.String("bar") assert.True(k1.Equal(&k2)) assert.True(k1.EqualWithOpts(&k2, false, false, false)) k1.Tags = getTags(true) k2.Tags = getTags(false) assert.True(k1.EqualWithOpts(&k2, false, false, false)) k1.ID = kong.String("fuu") assert.False(k1.EqualWithOpts(&k2, false, false, false)) assert.True(k1.EqualWithOpts(&k2, true, false, false)) k2.CreatedAt = kong.Int(1) assert.False(k1.EqualWithOpts(&k2, false, false, false)) assert.False(k1.EqualWithOpts(&k2, false, true, false)) k2.Consumer = &kong.Consumer{Username: kong.String("u1")} assert.False(k1.EqualWithOpts(&k2, false, true, false)) assert.False(k1.EqualWithOpts(&k2, false, false, true)) } func TestStripKey(t *testing.T) { assert := assert.New(t) assert.Equal("hello", stripKey("hello")) assert.Equal("yolo", stripKey("yolo")) assert.Equal("world", stripKey("hello world")) } deck-1.4.0/state/upstream.go000066400000000000000000000070521400603563700157410ustar00rootroot00000000000000package state import ( "fmt" memdb "github.com/hashicorp/go-memdb" "github.com/kong/deck/utils" ) const ( upstreamTableName = "upstream" ) var upstreamTableSchema = &memdb.TableSchema{ Name: upstreamTableName, Indexes: map[string]*memdb.IndexSchema{ "id": { Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "ID"}, }, "name": { Name: "name", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "Name"}, }, all: allIndex, }, } // UpstreamsCollection stores and indexes Kong Upstreams. type UpstreamsCollection collection // Add adds an upstream to the collection. // upstream.ID should not be nil else an error is thrown. func (k *UpstreamsCollection) Add(upstream Upstream) error { // TODO abstract this check in the go-memdb library itself if utils.Empty(upstream.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() var searchBy []string searchBy = append(searchBy, *upstream.ID) if !utils.Empty(upstream.Name) { searchBy = append(searchBy, *upstream.Name) } _, err := getUpstream(txn, searchBy...) if err == nil { return fmt.Errorf("inserting upstream %v: %w", upstream.Console(), ErrAlreadyExists) } else if err != ErrNotFound { return err } err = txn.Insert(upstreamTableName, &upstream) if err != nil { return err } txn.Commit() return nil } func getUpstream(txn *memdb.Txn, IDs ...string) (*Upstream, error) { for _, id := range IDs { res, err := multiIndexLookupUsingTxn(txn, upstreamTableName, []string{"name", "id"}, id) if err == ErrNotFound { continue } if err != nil { return nil, err } upstream, ok := res.(*Upstream) if !ok { panic(unexpectedType) } return &Upstream{Upstream: *upstream.DeepCopy()}, nil } return nil, ErrNotFound } // Get gets an upstream by name or ID. func (k *UpstreamsCollection) Get(nameOrID string) (*Upstream, error) { if nameOrID == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() upstream, err := getUpstream(txn, nameOrID) if err != nil { if err == ErrNotFound { return nil, ErrNotFound } return nil, err } return upstream, nil } // Update udpates an existing upstream. func (k *UpstreamsCollection) Update(upstream Upstream) error { // TODO abstract this in the go-memdb library itself if utils.Empty(upstream.ID) { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteUpstream(txn, *upstream.ID) if err != nil { return err } err = txn.Insert(upstreamTableName, &upstream) if err != nil { return err } txn.Commit() return nil } func deleteUpstream(txn *memdb.Txn, nameOrID string) error { upstream, err := getUpstream(txn, nameOrID) if err != nil { return err } err = txn.Delete(upstreamTableName, upstream) if err != nil { return err } return nil } // Delete deletes an upstream by it's name or ID. func (k *UpstreamsCollection) Delete(nameOrID string) error { if nameOrID == "" { return errIDRequired } txn := k.db.Txn(true) defer txn.Abort() err := deleteUpstream(txn, nameOrID) if err != nil { return err } txn.Commit() return nil } // GetAll gets all upstreams in the state. func (k *UpstreamsCollection) GetAll() ([]*Upstream, error) { txn := k.db.Txn(false) defer txn.Abort() iter, err := txn.Get(upstreamTableName, all, true) if err != nil { return nil, err } var res []*Upstream for el := iter.Next(); el != nil; el = iter.Next() { u, ok := el.(*Upstream) if !ok { panic(unexpectedType) } res = append(res, &Upstream{Upstream: *u.DeepCopy()}) } txn.Commit() return res, nil } deck-1.4.0/state/upstream_test.go000066400000000000000000000072471400603563700170060ustar00rootroot00000000000000package state import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func upstreamsCollection() *UpstreamsCollection { return state().Upstreams } func TestUpstreamInsert(t *testing.T) { assert := assert.New(t) collection := upstreamsCollection() // name is required var upstream Upstream upstream.ID = kong.String("first") err := collection.Add(upstream) assert.NotNil(err) // happy path upstream.Name = kong.String("my-upstream") assert.Nil(collection.Add(upstream)) // ID is required var upstream2 Upstream upstream2.Name = kong.String("my-upstream") err = collection.Add(upstream2) assert.NotNil(err) // re-insert upstream2.ID = kong.String("first") assert.NotNil(collection.Add(upstream2)) upstream2.ID = kong.String("same-name-but-different-id") assert.NotNil(collection.Add(upstream2)) } func TestUpstreamGetUpdate(t *testing.T) { assert := assert.New(t) collection := upstreamsCollection() se, err := collection.Get("does-not-exist") assert.NotNil(err) assert.Nil(se) se, err = collection.Get("") assert.NotNil(err) assert.Nil(se) var upstream Upstream upstream.Name = kong.String("my-upstream") upstream.ID = kong.String("first") err = collection.Add(upstream) assert.Nil(err) se, err = collection.Get("first") assert.Nil(err) assert.NotNil(se) se.Name = kong.String("my-updated-upstream") err = collection.Update(*se) assert.Nil(err) se, err = collection.Get("my-updated-upstream") assert.Nil(err) assert.NotNil(se) se.ID = nil err = collection.Update(*se) assert.NotNil(err) se, err = collection.Get("my-upstream") assert.Equal(ErrNotFound, err) assert.Nil(se) } // Regression test // to ensure that the memory reference of the pointer returned by Get() // is different from the one stored in MemDB. func TestUpstreamGetMemoryReference(t *testing.T) { assert := assert.New(t) collection := upstreamsCollection() var upstream Upstream upstream.Name = kong.String("my-upstream") upstream.ID = kong.String("first") err := collection.Add(upstream) assert.Nil(err) se, err := collection.Get("first") assert.Nil(err) assert.NotNil(se) se.Slots = kong.Int(1) se, err = collection.Get("my-upstream") assert.Nil(err) assert.NotNil(se) assert.Nil(se.Slots) } func TestUpstreamsInvalidType(t *testing.T) { assert := assert.New(t) collection := upstreamsCollection() var route Route route.Name = kong.String("my-route") route.ID = kong.String("first") txn := collection.db.Txn(true) txn.Insert(upstreamTableName, &route) txn.Commit() assert.Panics(func() { collection.Get("my-route") }) assert.Panics(func() { collection.GetAll() }) } func TestUpstreamDelete(t *testing.T) { assert := assert.New(t) collection := upstreamsCollection() var upstream Upstream upstream.Name = kong.String("my-upstream") upstream.ID = kong.String("first") err := collection.Add(upstream) assert.Nil(err) se, err := collection.Get("my-upstream") assert.Nil(err) assert.NotNil(se) err = collection.Delete(*se.ID) assert.Nil(err) err = collection.Delete("") assert.NotNil(err) _, err = collection.Get("my-upstream") assert.Equal(ErrNotFound, err) err = collection.Delete(*se.ID) assert.NotNil(err) } func TestUpstreamGetAll(t *testing.T) { assert := assert.New(t) collection := upstreamsCollection() var upstream Upstream upstream.Name = kong.String("my-upstream1") upstream.ID = kong.String("first") err := collection.Add(upstream) assert.Nil(err) var upstream2 Upstream upstream2.Name = kong.String("my-upstream2") upstream2.ID = kong.String("second") err = collection.Add(upstream2) assert.Nil(err) upstreams, err := collection.GetAll() assert.Nil(err) assert.Equal(2, len(upstreams)) } deck-1.4.0/state/utils.go000066400000000000000000000022701400603563700152360ustar00rootroot00000000000000package state import ( memdb "github.com/hashicorp/go-memdb" "github.com/pkg/errors" ) const ( all = "all" ) // ErrNotFound is an error type that is // returned when an entity is not found in the state. var ErrNotFound = errors.New("entity not found") // ErrAlreadyExists represents an entity is already present in the state. var ErrAlreadyExists = errors.New("entity already exists") // internal errors var errIDRequired = errors.New("ID is required") // error annotation messages const ( unexpectedType = "unexpected type found" ) var allIndex = &memdb.IndexSchema{ Name: all, Indexer: &memdb.ConditionalIndex{ Conditional: func(v interface{}) (bool, error) { return true, nil }, }, } // multiIndexLookupUsingTxn can be used to search for an entity // based on search on multiple indexes with same key. func multiIndexLookupUsingTxn(txn *memdb.Txn, tableName string, indices []string, args ...interface{}) (interface{}, error) { for _, indexName := range indices { res, err := txn.First(tableName, indexName, args...) if res == nil && err == nil { continue } if err != nil { return nil, err } if res != nil { return res, nil } } return nil, ErrNotFound } deck-1.4.0/utils/000077500000000000000000000000001400603563700135665ustar00rootroot00000000000000deck-1.4.0/utils/constants.go000066400000000000000000000035611400603563700161360ustar00rootroot00000000000000package utils import "github.com/kong/go-kong/kong" const ( defaultPort = 80 defaultTimeout = 60000 defaultSlots = 10000 defaultWeight = 100 defaultConcurrency = 10 ) var ( serviceDefaults = kong.Service{ Port: kong.Int(defaultPort), Protocol: kong.String("http"), ConnectTimeout: kong.Int(defaultTimeout), WriteTimeout: kong.Int(defaultTimeout), ReadTimeout: kong.Int(defaultTimeout), } routeDefaults = kong.Route{ PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), } targetDefaults = kong.Target{ Weight: kong.Int(defaultWeight), } upstreamDefaults = kong.Upstream{ Slots: kong.Int(defaultSlots), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Concurrency: kong.Int(defaultConcurrency), Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 302}, Interval: kong.Int(0), Successes: kong.Int(0), }, HTTPPath: kong.String("/"), Type: kong.String("http"), Timeout: kong.Int(1), Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), Interval: kong.Int(0), HTTPStatuses: []int{429, 404, 500, 501, 502, 503, 504, 505}, }, }, Passive: &kong.PassiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308}, Successes: kong.Int(0), }, Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 500, 503}, }, }, }, HashOn: kong.String("none"), HashFallback: kong.String("none"), HashOnCookiePath: kong.String("/"), } ) deck-1.4.0/utils/defaulter.go000066400000000000000000000056731400603563700161030ustar00rootroot00000000000000package utils import ( "reflect" "github.com/imdario/mergo" "github.com/pkg/errors" ) // Defaulter registers types and fills in struct fields with // default values. type Defaulter struct { r map[string]interface{} } // GetKongDefaulter returns a defaulter which can set default values // for Kong entities. func GetKongDefaulter() (*Defaulter, error) { // TODO make defaults configurable // TODO add support for file based defaults var d Defaulter err := d.Register(&serviceDefaults) if err != nil { return nil, errors.Wrap(err, "registering service with defaulter") } err = d.Register(&routeDefaults) if err != nil { return nil, errors.Wrap(err, "registering route with defaulter") } err = d.Register(&upstreamDefaults) if err != nil { return nil, errors.Wrap(err, "registering upstream with defaulter") } err = d.Register(&targetDefaults) if err != nil { return nil, errors.Wrap(err, "registering target with defaulter") } return &d, nil } func (d *Defaulter) once() { if d.r == nil { d.r = make(map[string]interface{}) } } // Register registers a type and it's default value. // The default value is passed in and the type is inferred from the // default value. func (d *Defaulter) Register(def interface{}) error { d.once() v := reflect.ValueOf(def) if !v.IsValid() { return errors.New("invalid value") } v = reflect.Indirect(v) d.r[v.Type().String()] = def return nil } type kongTransformer struct { } func (t kongTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { var a *int var ar []int var b *bool switch typ { case reflect.TypeOf(ar): return func(dst, src reflect.Value) error { if dst.CanSet() { if reflect.DeepEqual(reflect.Zero(dst.Type()).Interface(), dst.Interface()) { return nil } } return nil } case reflect.TypeOf(a): return func(dst, src reflect.Value) error { if dst.CanSet() { if reflect.DeepEqual(reflect.Zero(dst.Type()).Interface(), dst.Interface()) { return nil } } return nil } case reflect.TypeOf(b): return func(dst, src reflect.Value) error { if dst.CanSet() { if reflect.DeepEqual(reflect.Zero(dst.Type()).Interface(), dst.Interface()) { return nil } } return nil } default: return nil } } // Set fills in default values in a struct of a registered type. func (d *Defaulter) Set(arg interface{}) error { d.once() v := reflect.ValueOf(arg) if !v.IsValid() { return errors.New("invalid value") } v = reflect.Indirect(v) defValue, ok := d.r[v.Type().String()] if !ok { return errors.New("type not registered: " + reflect.TypeOf(arg).String()) } err := mergo.Merge(arg, defValue, mergo.WithTransformers(kongTransformer{})) if err != nil { err = errors.Wrap(err, "merging") } return err // return defaulter.Set(arg, defValue) } // MustSet is like Set but panics if there is an error. func (d *Defaulter) MustSet(arg interface{}) { err := d.Set(arg) if err != nil { panic(err) } } deck-1.4.0/utils/defaulter_test.go000066400000000000000000000275551400603563700171450ustar00rootroot00000000000000package utils import ( "testing" "github.com/kong/go-kong/kong" "github.com/stretchr/testify/assert" ) func TestDefaulter(t *testing.T) { assert := assert.New(t) var d Defaulter assert.NotNil(d.Register(nil)) assert.NotNil(d.Set(nil)) assert.Panics(func() { d.MustSet(d) }) type Foo struct { A string B []string } defaultFoo := &Foo{ A: "defaultA", B: []string{"default1"}, } assert.Nil(d.Register(defaultFoo)) // sets a default var arg Foo assert.Nil(d.Set(&arg)) assert.Equal("defaultA", arg.A) assert.Equal([]string{"default1"}, arg.B) // doesn't set a default arg1 := Foo{ A: "non-default-value", } assert.Nil(d.Set(&arg1)) assert.Equal("non-default-value", arg1.A) // errors on an unregistered type type Bar struct { A string } assert.NotNil(d.Set(&Bar{})) assert.Panics(func() { d.MustSet(&Bar{}) }) } func TestServiceSetTest(t *testing.T) { assert := assert.New(t) d, err := GetKongDefaulter() assert.NotNil(d) assert.Nil(err) testCases := []struct { desc string arg *kong.Service want *kong.Service }{ { desc: "empty service", arg: &kong.Service{}, want: &serviceDefaults, }, { desc: "retries can be set to 0", arg: &kong.Service{ Retries: kong.Int(0), }, want: &kong.Service{ Port: kong.Int(80), Retries: kong.Int(0), Protocol: kong.String("http"), ConnectTimeout: kong.Int(60000), WriteTimeout: kong.Int(60000), ReadTimeout: kong.Int(60000), }, }, { desc: "timeout value value is not overridden", arg: &kong.Service{ WriteTimeout: kong.Int(42), }, want: &kong.Service{ Port: kong.Int(80), Protocol: kong.String("http"), ConnectTimeout: kong.Int(60000), WriteTimeout: kong.Int(42), ReadTimeout: kong.Int(60000), }, }, { desc: "path value is not overridden", arg: &kong.Service{ Path: kong.String("/foo"), }, want: &kong.Service{ Port: kong.Int(80), Protocol: kong.String("http"), Path: kong.String("/foo"), ConnectTimeout: kong.Int(60000), WriteTimeout: kong.Int(60000), ReadTimeout: kong.Int(60000), }, }, { desc: "Name is not reset", arg: &kong.Service{ Name: kong.String("foo"), Host: kong.String("example.com"), Path: kong.String("/bar"), }, want: &kong.Service{ Name: kong.String("foo"), Host: kong.String("example.com"), Port: kong.Int(80), Protocol: kong.String("http"), Path: kong.String("/bar"), ConnectTimeout: kong.Int(60000), WriteTimeout: kong.Int(60000), ReadTimeout: kong.Int(60000), }, }, } for _, tC := range testCases { t.Run(tC.desc, func(t *testing.T) { err := d.Set(tC.arg) assert.Nil(err) assert.Equal(tC.want, tC.arg) }) } } func TestRouteSetTest(t *testing.T) { assert := assert.New(t) d, err := GetKongDefaulter() assert.NotNil(d) assert.Nil(err) testCases := []struct { desc string arg *kong.Route want *kong.Route }{ { desc: "empty route", arg: &kong.Route{}, want: &routeDefaults, }, { desc: "preserve host is not overridden", arg: &kong.Route{ PreserveHost: kong.Bool(true), }, want: &kong.Route{ PreserveHost: kong.Bool(true), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), }, }, { desc: "Protocols is not reset", arg: &kong.Route{ Protocols: kong.StringSlice("http", "tls"), }, want: &kong.Route{ PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "tls"), }, }, { desc: "non-default feilds is not reset", arg: &kong.Route{ Name: kong.String("foo"), Hosts: kong.StringSlice("1.example.com", "2.example.com"), Methods: kong.StringSlice("GET", "POST"), StripPath: kong.Bool(false), }, want: &kong.Route{ Name: kong.String("foo"), Hosts: kong.StringSlice("1.example.com", "2.example.com"), Methods: kong.StringSlice("GET", "POST"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), }, }, { desc: "strip-path can be set to false", arg: &kong.Route{ StripPath: kong.Bool(false), }, want: &kong.Route{ PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(false), Protocols: kong.StringSlice("http", "https"), }, }, { desc: "strip-path can be set to true", arg: &kong.Route{ StripPath: kong.Bool(true), }, want: &kong.Route{ PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), StripPath: kong.Bool(true), Protocols: kong.StringSlice("http", "https"), }, }, } for _, tC := range testCases { t.Run(tC.desc, func(t *testing.T) { err := d.Set(tC.arg) assert.Nil(err) assert.Equal(tC.want, tC.arg) }) } } func TestUpstreamSetTest(t *testing.T) { assert := assert.New(t) d, err := GetKongDefaulter() assert.NotNil(d) assert.Nil(err) testCases := []struct { desc string arg *kong.Upstream want *kong.Upstream }{ { desc: "empty upstream", arg: &kong.Upstream{}, want: &upstreamDefaults, }, { desc: "Healthchecks.Active.Healthy.HTTPStatuses is not overridden", arg: &kong.Upstream{ Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200}, }, }, }, }, want: &kong.Upstream{ Slots: kong.Int(10000), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Concurrency: kong.Int(10), Healthy: &kong.Healthy{ HTTPStatuses: []int{200}, Interval: kong.Int(0), Successes: kong.Int(0), }, HTTPPath: kong.String("/"), Type: kong.String("http"), Timeout: kong.Int(1), Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 404, 500, 501, 502, 503, 504, 505}, Interval: kong.Int(0), }, }, Passive: &kong.PassiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308}, Successes: kong.Int(0), }, Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 500, 503}, }, }, }, HashOn: kong.String("none"), HashFallback: kong.String("none"), HashOnCookiePath: kong.String("/"), }, }, { desc: "Healthchecks.Active.Healthy.Timeout is not overridden", arg: &kong.Upstream{ Name: kong.String("foo"), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Healthy: &kong.Healthy{ Interval: kong.Int(1), }, }, }, }, want: &kong.Upstream{ Name: kong.String("foo"), Slots: kong.Int(10000), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Concurrency: kong.Int(10), Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 302}, Interval: kong.Int(1), Successes: kong.Int(0), }, HTTPPath: kong.String("/"), Type: kong.String("http"), Timeout: kong.Int(1), Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 404, 500, 501, 502, 503, 504, 505}, Interval: kong.Int(0), }, }, Passive: &kong.PassiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308}, Successes: kong.Int(0), }, Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 500, 503}, }, }, }, HashOn: kong.String("none"), HashFallback: kong.String("none"), HashOnCookiePath: kong.String("/"), }, }, { desc: "Healthchecks.Active.HTTPSVerifyCertificate can be set to false", arg: &kong.Upstream{ Name: kong.String("foo"), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Healthy: &kong.Healthy{ Interval: kong.Int(1), }, HTTPSVerifyCertificate: kong.Bool(false), }, }, }, want: &kong.Upstream{ Name: kong.String("foo"), Slots: kong.Int(10000), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Concurrency: kong.Int(10), Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 302}, Interval: kong.Int(1), Successes: kong.Int(0), }, HTTPPath: kong.String("/"), HTTPSVerifyCertificate: kong.Bool(false), Type: kong.String("http"), Timeout: kong.Int(1), Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 404, 500, 501, 502, 503, 504, 505}, Interval: kong.Int(0), }, }, Passive: &kong.PassiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308}, Successes: kong.Int(0), }, Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 500, 503}, }, }, }, HashOn: kong.String("none"), HashFallback: kong.String("none"), HashOnCookiePath: kong.String("/"), }, }, { desc: "Healthchecks.Active.HTTPSVerifyCertificate can be set to true", arg: &kong.Upstream{ Name: kong.String("foo"), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Healthy: &kong.Healthy{ Interval: kong.Int(1), }, HTTPSVerifyCertificate: kong.Bool(true), }, }, }, want: &kong.Upstream{ Name: kong.String("foo"), Slots: kong.Int(10000), Healthchecks: &kong.Healthcheck{ Active: &kong.ActiveHealthcheck{ Concurrency: kong.Int(10), Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 302}, Interval: kong.Int(1), Successes: kong.Int(0), }, HTTPPath: kong.String("/"), HTTPSVerifyCertificate: kong.Bool(true), Type: kong.String("http"), Timeout: kong.Int(1), Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 404, 500, 501, 502, 503, 504, 505}, Interval: kong.Int(0), }, }, Passive: &kong.PassiveHealthcheck{ Healthy: &kong.Healthy{ HTTPStatuses: []int{200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308}, Successes: kong.Int(0), }, Unhealthy: &kong.Unhealthy{ HTTPFailures: kong.Int(0), TCPFailures: kong.Int(0), Timeouts: kong.Int(0), HTTPStatuses: []int{429, 500, 503}, }, }, }, HashOn: kong.String("none"), HashFallback: kong.String("none"), HashOnCookiePath: kong.String("/"), }, }, } for _, tC := range testCases { t.Run(tC.desc, func(t *testing.T) { err := d.Set(tC.arg) assert.Nil(err) assert.Equal(tC.want, tC.arg) }) } } deck-1.4.0/utils/tags.go000066400000000000000000000035211400603563700150540ustar00rootroot00000000000000package utils import ( "reflect" "github.com/pkg/errors" ) // MustMergeTags is same as MergeTags but panics if there is an error. func MustMergeTags(obj interface{}, tags []string) { err := MergeTags(obj, tags) if err != nil { panic(err) } } // MergeTags merges Tags in the object with tags. func MergeTags(obj interface{}, tags []string) error { if len(tags) == 0 { return nil } ptr := reflect.ValueOf(obj) if ptr.Kind() != reflect.Ptr { return errors.New("obj is not a pointer") } v := reflect.Indirect(ptr) structTags := v.FieldByName("Tags") var zero reflect.Value if structTags == zero { return nil } m := make(map[string]bool) for i := 0; i < structTags.Len(); i++ { tag := reflect.Indirect(structTags.Index(i)).String() m[tag] = true } for _, tag := range tags { if _, ok := m[tag]; !ok { t := tag structTags.Set(reflect.Append(structTags, reflect.ValueOf(&t))) } } return nil } // MustRemoveTags is same as RemoveTags but panics if there is an error. func MustRemoveTags(obj interface{}, tags []string) { err := RemoveTags(obj, tags) if err != nil { panic(err) } } // RemoveTags removes tags from the Tags in obj. func RemoveTags(obj interface{}, tags []string) error { if len(tags) == 0 { return nil } m := make(map[string]bool) for _, tag := range tags { m[tag] = true } ptr := reflect.ValueOf(obj) if ptr.Kind() != reflect.Ptr { return errors.New("obj is not a pointer") } v := reflect.Indirect(ptr) structTags := v.FieldByName("Tags") var zero reflect.Value if structTags == zero { return nil } res := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(reflect.TypeOf(""))), 0, 0) for i := 0; i < structTags.Len(); i++ { tag := reflect.Indirect(structTags.Index(i)).String() if !m[tag] { res = reflect.Append(res, structTags.Index(i)) } } structTags.Set(res) return nil } deck-1.4.0/utils/tags_test.go000066400000000000000000000033611400603563700161150ustar00rootroot00000000000000package utils import ( "testing" "github.com/stretchr/testify/assert" ) func TestMergeTags(t *testing.T) { type Foo struct { Tags []*string } type Bar struct{} assert := assert.New(t) a := "tag1" b := "tag2" c := "tag3" var f Foo err := MergeTags(f, []string{"tag1"}) assert.NotNil(err) assert.Panics(func() { MustMergeTags(f, []string{"tag1"}) }) var bar Bar err = MergeTags(&bar, []string{"tag1"}) assert.Nil(err) f = Foo{Tags: []*string{&a, &b}} assert.Nil(MergeTags(&f, []string{"tag1", "tag2", "tag3"})) assert.True(equalArray([]*string{&a, &b, &c}, f.Tags)) f = Foo{Tags: []*string{}} assert.Nil(MergeTags(&f, []string{"tag1", "tag2", "tag3"})) assert.True(equalArray([]*string{&a, &b, &c}, f.Tags)) f = Foo{Tags: []*string{&a, &b}} assert.Nil(MergeTags(&f, nil)) assert.True(equalArray([]*string{&a, &b}, f.Tags)) } func equalArray(want, have []*string) bool { if len(want) != len(have) { return false } for i := 0; i < len(want); i++ { if *want[i] != *have[i] { return false } } return true } func TestRemoveTags(t *testing.T) { type Foo struct { Tags []*string } type Bar struct{} assert := assert.New(t) a := "tag1" b := "tag2" var f Foo err := RemoveTags(f, []string{"tag1"}) assert.NotNil(err) assert.Panics(func() { MustRemoveTags(f, []string{"tag1"}) }) var bar Bar err = RemoveTags(&bar, []string{"tag1"}) assert.Nil(err) f = Foo{Tags: []*string{&a, &b}} RemoveTags(&f, []string{"tag2", "tag3"}) assert.True(equalArray([]*string{&a}, f.Tags)) f = Foo{Tags: []*string{}} RemoveTags(&f, []string{"tag1", "tag2", "tag3"}) assert.True(equalArray([]*string{}, f.Tags)) f = Foo{Tags: []*string{&a, &b}} RemoveTags(&f, nil) assert.True(equalArray([]*string{&a, &b}, f.Tags)) } deck-1.4.0/utils/types.go000066400000000000000000000074201400603563700152640ustar00rootroot00000000000000package utils import ( "crypto/tls" "crypto/x509" "fmt" "net/http" "net/url" "os" "path" "regexp" "strconv" "strings" "github.com/kong/go-kong/kong" "github.com/kong/go-kong/kong/custom" "github.com/pkg/errors" ) // KongRawState contains all of Kong Data type KongRawState struct { Services []*kong.Service Routes []*kong.Route Plugins []*kong.Plugin Upstreams []*kong.Upstream Targets []*kong.Target Certificates []*kong.Certificate SNIs []*kong.SNI CACertificates []*kong.CACertificate Consumers []*kong.Consumer CustomEntities []*custom.Entity KeyAuths []*kong.KeyAuth HMACAuths []*kong.HMACAuth JWTAuths []*kong.JWTAuth BasicAuths []*kong.BasicAuth ACLGroups []*kong.ACLGroup Oauth2Creds []*kong.Oauth2Credential MTLSAuths []*kong.MTLSAuth } // ErrArray holds an array of errors. type ErrArray struct { Errors []error } // Error returns a pretty string of errors present. func (e ErrArray) Error() string { if len(e.Errors) == 0 { return "nil" } var res string res = strconv.Itoa(len(e.Errors)) + " errors occurred:\n" for _, err := range e.Errors { res += fmt.Sprintf("\t%v\n", err) } return res } // KongClientConfig holds config details to use to talk to a Kong server. type KongClientConfig struct { Address string Workspace string TLSServerName string TLSCACert string TLSSkipVerify bool Debug bool SkipWorkspaceCrud bool Headers []string } // ForWorkspace returns a copy of KongClientConfig that produces a KongClient for the workspace specified by argument. func (kc *KongClientConfig) ForWorkspace(name string) KongClientConfig { result := *kc result.Workspace = name return result } // HeaderRoundTripper injects Headers into requests // made via RT. type HeaderRoundTripper struct { headers []string rt http.RoundTripper } // RoundTrip satisfies the RoundTripper interface. func (t *HeaderRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { newRequest := new(http.Request) *newRequest = *req newRequest.Header = make(http.Header, len(req.Header)) for k, s := range req.Header { newRequest.Header[k] = append([]string(nil), s...) } for _, s := range t.headers { split := strings.SplitN(s, ":", 2) if len(split) >= 2 { newRequest.Header[split[0]] = append([]string(nil), split[1]) } } return t.rt.RoundTrip(newRequest) } // GetKongClient returns a Kong client func GetKongClient(opt KongClientConfig) (*kong.Client, error) { var tlsConfig tls.Config if opt.TLSSkipVerify { tlsConfig.InsecureSkipVerify = true } if opt.TLSServerName != "" { tlsConfig.ServerName = opt.TLSServerName } if opt.TLSCACert != "" { certPool := x509.NewCertPool() ok := certPool.AppendCertsFromPEM([]byte(opt.TLSCACert)) if !ok { return nil, errors.New("failed to load TLSCACert") } tlsConfig.RootCAs = certPool } c := &http.Client{} defaultTransport := http.DefaultTransport.(*http.Transport) defaultTransport.TLSClientConfig = &tlsConfig c.Transport = defaultTransport if len(opt.Headers) > 0 { c.Transport = &HeaderRoundTripper{ headers: opt.Headers, rt: defaultTransport, } } address := CleanAddress(opt.Address) url, err := url.ParseRequestURI(address) if err != nil { return nil, errors.Wrap(err, "failed to parse kong address") } if opt.Workspace != "" { url.Path = path.Join(url.Path, opt.Workspace) } kongClient, err := kong.NewClient(kong.String(url.String()), c) if err != nil { return nil, errors.Wrap(err, "creating client for Kong's Admin API") } if opt.Debug { kongClient.SetDebugMode(true) kongClient.SetLogger(os.Stderr) } return kongClient, nil } // CleanAddress removes trailling / from a URL. func CleanAddress(address string) string { re := regexp.MustCompile("[/]+$") return re.ReplaceAllString(address, "") } deck-1.4.0/utils/types_test.go000066400000000000000000000024641400603563700163260ustar00rootroot00000000000000package utils import ( "testing" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) func TestErrArrayString(t *testing.T) { assert := assert.New(t) var err ErrArray assert.Equal("nil", err.Error()) err.Errors = append(err.Errors, errors.New("foo failed")) assert.Equal(err.Error(), "1 errors occurred:\n\tfoo failed\n") err.Errors = append(err.Errors, errors.New("bar failed")) assert.Equal(err.Error(), "2 errors occurred:\n\tfoo failed\n\tbar failed\n") } func Test_cleanAddress(t *testing.T) { type args struct { address string } tests := []struct { name string args args want string }{ { args: args{ address: "foo", }, want: "foo", }, { args: args{ address: "http://localhost:8001", }, want: "http://localhost:8001", }, { args: args{ address: "http://localhost:8001/", }, want: "http://localhost:8001", }, { args: args{ address: "http://localhost:8001//", }, want: "http://localhost:8001", }, { args: args{ address: "https://subdomain.example.com///", }, want: "https://subdomain.example.com", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := CleanAddress(tt.args.address); got != tt.want { t.Errorf("cleanAddress() = %v, want %v", got, tt.want) } }) } } deck-1.4.0/utils/utils.go000066400000000000000000000027531400603563700152640ustar00rootroot00000000000000package utils import ( "crypto/rand" "encoding/hex" "fmt" "regexp" ) var ( kongVersionRegex = regexp.MustCompile(`^\d+\.\d+`) ) // Empty checks if a string referenced by s or s itself is empty. func Empty(s *string) bool { return s == nil || *s == "" } // UUID will generate a random v14 unique identifier based upon random numbers func UUID() string { version := byte(4) uuid := make([]byte, 16) _, err := rand.Read(uuid) if err != nil { panic("failed to read from random generator: " + err.Error()) } // Set version uuid[6] = (uuid[6] & 0x0f) | (version << 4) // Set variant uuid[8] = (uuid[8] & 0xbf) | 0x80 buf := make([]byte, 36) var dash byte = '-' hex.Encode(buf[0:8], uuid[0:4]) buf[8] = dash hex.Encode(buf[9:13], uuid[4:6]) buf[13] = dash hex.Encode(buf[14:18], uuid[6:8]) buf[18] = dash hex.Encode(buf[19:23], uuid[8:10]) buf[23] = dash hex.Encode(buf[24:], uuid[10:]) return string(buf) } // CleanKongVersion takes a version of Kong and returns back a string in // the form of `/major.minor` version. There are various dashes and dots // and other descriptors in Kong version strings, which has often created // confusion in code and incorrect parsing, and hence this function does // not return the patch version (on which shouldn't rely on anyways). func CleanKongVersion(version string) (string, error) { matches := kongVersionRegex.FindStringSubmatch(version) if len(matches) < 1 { return "", fmt.Errorf("unknown Kong version") } return matches[0], nil } deck-1.4.0/utils/utils_test.go000066400000000000000000000035401400603563700163160ustar00rootroot00000000000000package utils import ( "regexp" "testing" "github.com/stretchr/testify/assert" ) func TestEmpty(t *testing.T) { assert := assert.New(t) notEmpty := "not-empty" emptyString := "" var nilPointer *string assert.False(Empty(¬Empty)) assert.True(Empty(nilPointer)) assert.True(Empty(&emptyString)) } func TestUUID(t *testing.T) { assert := assert.New(t) uuid := UUID() assert.NotEmpty(uuid) assert.Regexp(regexp.MustCompile( "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), uuid) } func Test_cleanKongVersion(t *testing.T) { type args struct { version string } tests := []struct { name string args args want string wantErr bool }{ { args: args{ version: "1.0.1", }, want: "1.0", wantErr: false, }, { args: args{ version: "1.3.0.1", }, want: "1.3", wantErr: false, }, { args: args{ version: "0.14.1", }, want: "0.14", wantErr: false, }, { args: args{ version: "0.14.2rc", }, want: "0.14", wantErr: false, }, { args: args{ version: "0.14.2rc1", }, want: "0.14", wantErr: false, }, { args: args{ version: "0.33-enterprise-edition", }, want: "0.33", wantErr: false, }, { args: args{ version: "1.3.0-0-enterprise-edition", }, want: "1.3", wantErr: false, }, { args: args{ version: "", }, want: "", wantErr: true, }, { args: args{ version: "0-1.1", }, want: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := CleanKongVersion(tt.args.version) if (err != nil) != tt.wantErr { t.Errorf("cleanKongVersion() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("cleanKongVersion() = %v, want %v", got, tt.want) } }) } }