diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..9ae56e703 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..db6afde65 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +file/kong_json_schema.json linguist-generated=true diff --git a/.github/workflows/integration-enterprise.yaml b/.github/workflows/integration-enterprise.yaml index 80637d85b..2047a7096 100644 --- a/.github/workflows/integration-enterprise.yaml +++ b/.github/workflows/integration-enterprise.yaml @@ -23,6 +23,7 @@ jobs: - 'kong/kong-gateway:3.0' - 'kong/kong-gateway:3.1' - 'kong/kong-gateway:3.2' + - 'kong/kong-gateway:3.3' - 'kong/kong-gateway-dev:latest' env: KONG_ANONYMOUS_REPORTS: "off" diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 53d7d9d97..ed8d86174 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -25,6 +25,7 @@ jobs: - 'kong:3.0' - 'kong:3.1' - 'kong:3.2' + - 'kong:3.3' - 'kong/kong:master-alpine' env: KONG_ANONYMOUS_REPORTS: "off" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 58e6ece1b..36cc2b04d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -57,7 +57,7 @@ jobs: password: ${{ secrets.DOCKER_TOKEN }} - name: Docker meta id: meta - uses: docker/metadata-action@v4.4.0 + uses: docker/metadata-action@v4.6.0 with: images: kong/deck tags: ${{ env.TAGS_STANDARD }}${{ env.TAGS_SUPPLEMENTAL }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 696eb4097..c3a2929c8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,7 +17,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.4.0 + uses: golangci/golangci-lint-action@v3.6.0 - name: Verify Codegen run: make verify-codegen - name: Run tests with Coverage @@ -26,6 +26,7 @@ jobs: uses: codecov/codecov-action@v3 with: name: codecov-deck + token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true - name: Build run: make build diff --git a/.github/workflows/validate-kong-release.yaml b/.github/workflows/validate-kong-release.yaml new file mode 100644 index 000000000..af6c10715 --- /dev/null +++ b/.github/workflows/validate-kong-release.yaml @@ -0,0 +1,39 @@ +name: Validate Kong Gateway Release +concurrency: + group: ${{ github.workflow }} +on: + workflow_dispatch: + inputs: + kong_image: + description: 'Kong Gateway Docker Image' + required: true + default: 'kong/kong-gateway-dev:latest' + branch: + description: 'decK Branch' + required: true + default: 'main' +jobs: + integration: + name: "${{ inputs.kong_image }} against ${{ inputs.branch }}" + env: + KONG_ANONYMOUS_REPORTS: "off" + KONG_IMAGE: ${{ inputs.kong_image }} + KONG_LICENSE_DATA: ${{ secrets.KONG_LICENSE_DATA }} + runs-on: ubuntu-latest + steps: + - name: Execution Information + run: | + echo "Kong Gateway Image = ${{ inputs.kong_image }}" + echo "decK Branch = ${{ inputs.branch }}" + - name: Setup go + uses: actions/setup-go@v4 + with: + go-version: '^1.20' + - name: Checkout repository + uses: actions/checkout@v3 + with: + ref: ${{ inputs.branch }} + - name: Setup Kong + run: make setup-kong-ee + - name: Run integration tests + run: make test-integration diff --git a/.gitignore b/.gitignore index 9b579130f..80b2fc161 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,9 @@ _testmain.go deck dist/ docs/cli-docs/ +.idea/deck.iml +.idea/misc.xml +.gitignore +.idea/vcs.xml +.gitignore +.idea/modules.xml diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..5c7247b40 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index dbfa476ed..43d0cd05f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Table of Contents +- [v1.26.1](#v1261) +- [v1.26.0](#v1260) +- [v1.25.0](#v1250) +- [v1.24.0](#v1240) +- [v1.23.0](#v1230) - [v1.22.1](#v1221) - [v1.22.0](#v1220) - [v1.21.0](#v1210) @@ -59,6 +64,105 @@ - [v0.2.0](#v020) - [v0.1.0](#v010) +## [v1.26.1] + +> Release date: 2023/09/07 + +### Fixes + +- Raise an error if state files have different Runtime Groups + [#1014](https://github.com/Kong/deck/pull/1014) +- Correct consumers validation when `custom_id` is used + [#1012](https://github.com/Kong/deck/pull/1012) +- Remove hardcoded default value for Routes' `strip_path` field. Defaults are pulled via + API anyway. + [#999](https://github.com/Kong/deck/pull/999) + +## [v1.26.0] + +> Release date: 2023/08/09 + +### Added + +- Added support for scoping plugins to Consumer Groups for both Kong Gateway and Konnect. + [#963](https://github.com/Kong/deck/pull/963) + [#959](https://github.com/Kong/deck/pull/959) + +### Fixes + +- Remove fallback mechanism formely used to authenticate with either "old" or "new" Konnect. + [#995](https://github.com/Kong/deck/pull/995) + +## [v1.25.0] + +> Release date: 2023/07/28 + +### Added + +- Added a new command `file render` to render a final decK file. This will result in a file representing + the state as it would be synced online. + [#963](https://github.com/Kong/deck/pull/963) +- Added a new flag `--format` to `file convert` to enable JSON output. + [#963](https://github.com/Kong/deck/pull/963) + +### Fixes + +- Use same interface to pull Consumer Groups with Kong Gateway and Konnect. + This will help solving the issue of using tags with Consumer Groups when running against Konnect. + [#984](https://github.com/Kong/deck/pull/984) +- Fix Consumers handling when a consumer's `custom_id` is equal to the `username` of another consumer. + [#986](https://github.com/Kong/deck/pull/986) +- Avoid misleading diffs when configuration file has empty tags. + [#985](https://github.com/Kong/deck/pull/985) + +## [v1.24.0] + +> Release date: 2023/07/24 + +### Added + +- Add a new flag (`--json-output`) to enable JSON output when using `sync` and `diff` commands + [#798](https://github.com/Kong/deck/pull/798) +- Improved error logs coming from files validation against Kong's schemas. + [#976](https://github.com/Kong/deck/pull/976) +- Added a new command `file openapi2kong` that will generate a deck file from an OpenAPI + 3.0 spec. This is the replacement for the similar `inso` functionality. + The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops). + [#939](https://github.com/Kong/deck/pull/939) +- Added a new command `file merge` that will merge multiple deck files. The files will not be + validated, which allows for working with incomplete or even invalid files in a pipeline. + The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops). + [#939](https://github.com/Kong/deck/pull/939) +- Added a new command `file patch` for applying patches on top of a decK file. The patches can be + provided on the commandline, or via patch files. The deck file will not be + validated, which allows for working with incomplete or even invalid files in a pipeline. + The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops). + [#939](https://github.com/Kong/deck/pull/939) +- Added a new commands `file add-tags/list-tags/remove-tags` to manage tags in a decK file. The deck file will not be + validated, which allows for working with incomplete or even invalid files in a pipeline. + The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops). + [#939](https://github.com/Kong/deck/pull/939) +- Added a new command `file add-plugins` for adding plugins to a decK file. The plugins can be + provided on the commandline, or via config files. The deck file will not be + validated, which allows for working with incomplete or even invalid files in a pipeline. + The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops). + [#939](https://github.com/Kong/deck/pull/939) + +### Fixes + +- Fix Certificates & SNIs handling when running against Konnect. + [#978](https://github.com/Kong/deck/pull/978) + + +## [v1.23.0] + +> Release date: 2023/07/03 + +### Add + +- Honor HTTPS_PROXY and HTTP_PROXY proxy environment variables + [#952](https://github.com/Kong/deck/pull/952) + ## [v1.22.1] > Release date: 2023/06/22 @@ -1254,6 +1358,11 @@ No breaking changes have been introduced in this release. Debut release of decK +[v1.26.1]: https://github.com/kong/deck/compare/v1.26.0...v1.26.1 +[v1.26.0]: https://github.com/kong/deck/compare/v1.25.0...v1.26.0 +[v1.25.0]: https://github.com/kong/deck/compare/v1.24.0...v1.25.0 +[v1.24.0]: https://github.com/kong/deck/compare/v1.23.0...v1.24.0 +[v1.23.0]: https://github.com/kong/deck/compare/v1.22.1...v1.23.0 [v1.22.1]: https://github.com/kong/deck/compare/v1.22.0...v1.22.1 [v1.22.0]: https://github.com/kong/deck/compare/v1.21.0...v1.22.0 [v1.21.0]: https://github.com/kong/deck/compare/v1.20.0...v1.21.0 diff --git a/Dockerfile b/Dockerfile index fc8cbc547..aaae20c36 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20.5 AS build +FROM golang:1.21.0 AS build WORKDIR /deck COPY go.mod ./ COPY go.sum ./ @@ -9,7 +9,7 @@ ARG TAG RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o deck \ -ldflags "-s -w -X github.com/kong/deck/cmd.VERSION=$TAG -X github.com/kong/deck/cmd.COMMIT=$COMMIT" -FROM alpine:3.18.2 +FROM alpine:3.18.3 RUN adduser --disabled-password --gecos "" deckuser RUN apk --no-cache add ca-certificates jq USER deckuser diff --git a/Makefile b/Makefile index dc40d45f2..7346c2b93 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ test-all: lint test .PHONY: test test: - go test -race ./... + go test -race -count=1 ./... .PHONY: lint lint: @@ -47,6 +47,6 @@ setup-kong-ee: .PHONY: test-integration test-integration: - go test -v -tags=integration \ + go test -v -count=1 -tags=integration \ -race \ ./tests/integration/... \ No newline at end of file diff --git a/cmd/common.go b/cmd/common.go index ede3d0421..61cc62c09 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -2,6 +2,8 @@ package cmd import ( "context" + "encoding/json" + "errors" "fmt" "net/http" "os" @@ -39,6 +41,8 @@ const ( modeKongEnterprise ) +var jsonOutput diff.JSONOutputObject + func getMode(targetContent *file.Content) mode { if inKonnectMode(targetContent) { return modeKonnect @@ -71,20 +75,36 @@ func workspaceExists(ctx context.Context, config utils.KongClientConfig, workspa return exists, nil } -func getWorkspaceName(workspaceFlag string, targetContent *file.Content) string { +func getWorkspaceName(workspaceFlag string, targetContent *file.Content, + enableJSONOutput bool, +) string { if workspaceFlag != targetContent.Workspace && workspaceFlag != "" { - cprint.DeletePrintf("Warning: Workspace '%v' specified via --workspace flag is "+ - "different from workspace '%v' found in state file(s).\n", workspaceFlag, targetContent.Workspace) + warning := fmt.Sprintf("Workspace '%v' specified via --workspace flag is "+ + "different from workspace '%v' found in state file(s).", workspaceFlag, targetContent.Workspace) + if enableJSONOutput { + jsonOutput.Warnings = append(jsonOutput.Warnings, warning) + } else { + cprint.DeletePrintf("Warning: " + warning + "\n") + } return workspaceFlag } return targetContent.Workspace } func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, - delay int, workspace string, + delay int, workspace string, enableJSONOutput bool, ) error { // read target file - targetContent, err := file.GetContentFromFiles(filenames) + if enableJSONOutput { + jsonOutput.Errors = []string{} + jsonOutput.Warnings = []string{} + jsonOutput.Changes = diff.EntityChanges{ + Creating: []diff.EntityState{}, + Updating: []diff.EntityState{}, + Deleting: []diff.EntityState{}, + } + } + targetContent, err := file.GetContentFromFiles(filenames, false) if err != nil { return err } @@ -137,7 +157,7 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, // prepare to read the current state from Kong var wsConfig utils.KongClientConfig - workspaceName := getWorkspaceName(workspace, targetContent) + workspaceName := getWorkspaceName(workspace, targetContent, enableJSONOutput) wsConfig = rootConfig.ForWorkspace(workspaceName) // load Kong version after workspace @@ -192,6 +212,10 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, return err } + if utils.Kong340Version.LTE(parsedKongVersion) { + dumpConfig.IsConsumerGroupScopedPluginSupported = true + } + // read the current state var currentState *state.KongState if workspaceExists { @@ -206,7 +230,15 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, return err } - cprint.CreatePrintln("creating workspace", wsConfig.Workspace) + if enableJSONOutput { + workspace := diff.EntityState{ + Name: wsConfig.Workspace, + Kind: "workspace", + } + jsonOutput.Changes.Creating = append(jsonOutput.Changes.Creating, workspace) + } else { + cprint.CreatePrintln("Creating workspace", wsConfig.Workspace) + } if !dry { _, err = rootClient.Workspaces.Create(ctx, &kong.Workspace{Name: &wsConfig.Workspace}) if err != nil { @@ -232,14 +264,34 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, } totalOps, err := performDiff( - ctx, currentState, targetState, dry, parallelism, delay, kongClient, mode == modeKonnect) + ctx, currentState, targetState, dry, parallelism, delay, kongClient, mode == modeKonnect, enableJSONOutput) if err != nil { - return err + if enableJSONOutput { + var errs utils.ErrArray + if errors.As(err, &errs) { + jsonOutput.Errors = append(jsonOutput.Errors, errs.ErrorList()...) + } else { + jsonOutput.Errors = append(jsonOutput.Errors, err.Error()) + } + } else { + return err + } } - if diffCmdNonZeroExitCode && totalOps > 0 { os.Exit(exitCodeDiffDetection) } + if enableJSONOutput { + jsonOutputBytes, jsonErr := json.MarshalIndent(jsonOutput, "", "\t") + if jsonErr != nil { + return err + } + jsonOutputString := string(jsonOutputBytes) + if !noMaskValues { + jsonOutputString = diff.MaskEnvVarValue(jsonOutputString) + } + + cprint.BluePrintLn(jsonOutputString + "\n") + } return nil } @@ -281,6 +333,7 @@ func fetchCurrentState(ctx context.Context, client *kong.Client, dumpConfig dump func performDiff(ctx context.Context, currentState, targetState *state.KongState, dry bool, parallelism int, delay int, client *kong.Client, isKonnect bool, + enableJSONOutput bool, ) (int, error) { s, err := diff.NewSyncer(diff.SyncerOpts{ CurrentState: currentState, @@ -294,13 +347,29 @@ func performDiff(ctx context.Context, currentState, targetState *state.KongState return 0, err } - stats, errs := s.Solve(ctx, parallelism, dry) + stats, errs, changes := s.Solve(ctx, parallelism, dry, enableJSONOutput) // print stats before error to report completed operations - printStats(stats) + if !enableJSONOutput { + printStats(stats) + } if errs != nil { return 0, utils.ErrArray{Errors: errs} } totalOps := stats.CreateOps.Count() + stats.UpdateOps.Count() + stats.DeleteOps.Count() + + if enableJSONOutput { + jsonOutput.Changes = diff.EntityChanges{ + Creating: append(jsonOutput.Changes.Creating, changes.Creating...), + Updating: append(jsonOutput.Changes.Updating, changes.Updating...), + Deleting: append(jsonOutput.Changes.Deleting, changes.Deleting...), + } + jsonOutput.Summary = diff.Summary{ + Creating: stats.CreateOps.Count(), + Updating: stats.UpdateOps.Count(), + Deleting: stats.DeleteOps.Count(), + Total: totalOps, + } + } return int(totalOps), nil } diff --git a/cmd/common_konnect.go b/cmd/common_konnect.go index f6a2b5f1b..850af8391 100644 --- a/cmd/common_konnect.go +++ b/cmd/common_konnect.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/http" - "net/url" "os" "strings" @@ -18,27 +17,12 @@ import ( "golang.org/x/sync/errgroup" ) -const ( - defaultLegacyKonnectURL = "https://konnect.konghq.com" - - defaultRuntimeGroupName = "default" - konnectWithRuntimeGroupsDomain = "api.konghq" -) - -var addresses = []string{ - defaultKonnectURL, - defaultLegacyKonnectURL, -} +const defaultRuntimeGroupName = "default" func authenticate( - ctx context.Context, client *konnect.Client, host string, konnectConfig utils.KonnectConfig, + ctx context.Context, client *konnect.Client, konnectConfig utils.KonnectConfig, ) (konnect.AuthResponse, error) { - if strings.Contains(host, konnectWithRuntimeGroupsDomain) { - return client.Auth.LoginV2(ctx, konnectConfig.Email, - konnectConfig.Password, konnectConfig.Token) - } - return client.Auth.Login(ctx, konnectConfig.Email, - konnectConfig.Password) + return client.Auth.LoginV2(ctx, konnectConfig.Email, konnectConfig.Password, konnectConfig.Token) } // GetKongClientForKonnectMode abstracts the different cloud environments users @@ -50,9 +34,6 @@ func GetKongClientForKonnectMode( ctx context.Context, konnectConfig *utils.KonnectConfig, ) (*kong.Client, error) { httpClient := utils.HTTPClient() - if konnectConfig.Address != defaultKonnectURL { - addresses = []string{konnectConfig.Address} - } if konnectConfig.Token != "" { konnectConfig.Headers = append( @@ -63,57 +44,25 @@ func GetKongClientForKonnectMode( // authenticate with konnect var err error var konnectClient *konnect.Client - var parsedAddress *url.URL var konnectAddress string - for _, address := range addresses { - // get Konnect client - konnectConfig.Address = address - konnectClient, err = utils.GetKonnectClient(httpClient, *konnectConfig) - if err != nil { - return nil, err - } - parsedAddress, err = url.Parse(address) - if err != nil { - return nil, fmt.Errorf("parsing %s address: %w", address, err) - } - _, err = authenticate(ctx, konnectClient, parsedAddress.Host, *konnectConfig) - if err == nil { - break - } - // Personal Access Token authentication is not supported with the - // legacy Konnect, so we don't need to fallback in case of 401s. - if konnect.IsUnauthorizedErr(err) && konnectConfig.Token != "" { - return nil, fmt.Errorf("authenticating with Konnect: %w", err) - } - if konnect.IsUnauthorizedErr(err) { - continue - } + // get Konnect client + konnectClient, err = utils.GetKonnectClient(httpClient, *konnectConfig) + if err != nil { + return nil, err } + _, err = authenticate(ctx, konnectClient, *konnectConfig) if err != nil { return nil, fmt.Errorf("authenticating with Konnect: %w", err) } - if strings.Contains(parsedAddress.Host, konnectWithRuntimeGroupsDomain) { - // get kong runtime group ID - kongRGID, err := fetchKongRuntimeGroupID(ctx, konnectClient) - if err != nil { - return nil, err - } - - // set the kong runtime group ID in the client - konnectClient.SetRuntimeGroupID(kongRGID) - konnectAddress = konnectConfig.Address + "/konnect-api/api/runtime_groups/" + kongRGID - } else { - // get kong control plane ID - kongCPID, err := fetchKongControlPlaneID(ctx, konnectClient) - if err != nil { - return nil, err - } - - // set the kong control plane ID in the client - konnectClient.SetControlPlaneID(kongCPID) - konnectAddress = konnectConfig.Address + "/api/control_planes/" + kongCPID + kongRGID, err := fetchKongRuntimeGroupID(ctx, konnectClient) + if err != nil { + return nil, err } + // set the kong runtime group ID in the client + konnectClient.SetRuntimeGroupID(kongRGID) + konnectAddress = konnectConfig.Address + "/konnect-api/api/runtime_groups/" + kongRGID + // initialize kong client return utils.GetKongClient(utils.KongClientConfig{ Address: konnectAddress, @@ -140,7 +89,7 @@ func resetKonnectV2(ctx context.Context) error { if err != nil { return err } - _, err = performDiff(ctx, currentState, targetState, false, 10, 0, client, true) + _, err = performDiff(ctx, currentState, targetState, false, 10, 0, client, true, resetJSONOutput) if err != nil { return err } @@ -183,7 +132,7 @@ func syncKonnect(ctx context.Context, httpClient := utils.HTTPClient() // read target file - targetContent, err := file.GetContentFromFiles(filenames) + targetContent, err := file.GetContentFromFiles(filenames, false) if err != nil { return err } @@ -258,7 +207,7 @@ func syncKonnect(ctx context.Context, return err } - stats, errs := s.Solve(ctx, parallelism, dry) + stats, errs, _ := s.Solve(ctx, parallelism, dry, false) // print stats before error to report completed operations printStats(stats) if errs != nil { diff --git a/cmd/convert.go b/cmd/convert.go deleted file mode 100644 index 0476ab2e2..000000000 --- a/cmd/convert.go +++ /dev/null @@ -1,96 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/kong/deck/convert" - "github.com/kong/deck/cprint" - "github.com/kong/deck/utils" - "github.com/spf13/cobra" -) - -var ( - convertCmdSourceFormat string - convertCmdDestinationFormat string - convertCmdInputFile string - convertCmdOutputFile string - convertCmdAssumeYes bool -) - -// newConvertCmd represents the convert command -func newConvertCmd() *cobra.Command { - convertCmd := &cobra.Command{ - Use: "convert", - Short: "Convert files from one format into another format", - Long: `The convert command changes configuration files from one format -into another compatible format. For example, a configuration for 'kong-gateway-2.x' -can be converted into a 'kong-gateway-3.x' configuration file.`, - Args: validateNoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - sourceFormat, err := convert.ParseFormat(convertCmdSourceFormat) - if err != nil { - return err - } - destinationFormat, err := convert.ParseFormat(convertCmdDestinationFormat) - if err != nil { - return err - } - - if convertCmdInputFile != "" { - if yes, err := utils.ConfirmFileOverwrite( - convertCmdOutputFile, "", convertCmdAssumeYes, - ); err != nil { - return err - } else if !yes { - return nil - } - - err = convert.Convert(convertCmdInputFile, convertCmdOutputFile, sourceFormat, destinationFormat) - if err != nil { - return fmt.Errorf("converting file: %w", err) - } - } else if is2xTo3xConversion() { - path, err := os.Getwd() - if err != nil { - return fmt.Errorf("getting current working directory: %w", err) - } - files, err := utils.ConfigFilesInDir(path) - if err != nil { - return fmt.Errorf("getting files from directory: %w", err) - } - for _, filename := range files { - err = convert.Convert(filename, filename, sourceFormat, destinationFormat) - if err != nil { - return fmt.Errorf("converting '%s' file: %w", filename, err) - } - } - } - if convertCmdDestinationFormat == "konnect" { - cprint.UpdatePrintf("Warning: konnect format type was deprecated in v1.12 and it will be removed\n" + - "in a future version. Please use your Kong configuration files with deck .\n" + - "Please see https://docs.konghq.com/konnect/getting-started/import/.\n") - } - return nil - }, - } - - sourceFormats := []convert.Format{convert.FormatKongGateway, convert.FormatKongGateway2x} - destinationFormats := []convert.Format{convert.FormatKonnect, convert.FormatKongGateway3x} - convertCmd.Flags().StringVar(&convertCmdSourceFormat, "from", "", - fmt.Sprintf("format of the source file, allowed formats: %v", sourceFormats)) - convertCmd.Flags().StringVar(&convertCmdDestinationFormat, "to", "", - fmt.Sprintf("desired format of the output, allowed formats: %v", destinationFormats)) - convertCmd.Flags().StringVar(&convertCmdInputFile, "input-file", "", - "configuration file to be converted. Use `-` to read from stdin.") - convertCmd.Flags().StringVar(&convertCmdOutputFile, "output-file", "kong.yaml", - "file to write configuration to after conversion. Use `-` to write to stdout.") - convertCmd.Flags().BoolVar(&convertCmdAssumeYes, "yes", - false, "assume `yes` to prompts and run non-interactively.") - return convertCmd -} - -func is2xTo3xConversion() bool { - return convertCmdSourceFormat == string(convert.FormatKongGateway2x) && - convertCmdDestinationFormat == string(convert.FormatKongGateway3x) -} diff --git a/cmd/diff.go b/cmd/diff.go index 8bb66ff92..b98df6692 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -11,6 +11,7 @@ var ( diffCmdParallelism int diffCmdNonZeroExitCode bool diffWorkspace string + diffJSONOutput bool ) // newDiffCmd represents the diff command @@ -27,7 +28,7 @@ that will be created, updated, or deleted. Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { return syncMain(cmd.Context(), diffCmdKongStateFile, true, - diffCmdParallelism, 0, diffWorkspace) + diffCmdParallelism, 0, diffWorkspace, diffJSONOutput) }, PreRunE: func(cmd *cobra.Command, args []string) error { if len(diffCmdKongStateFile) == 0 { @@ -65,6 +66,8 @@ that will be created, updated, or deleted. "and exit code 1 if an error occurs.") diffCmd.Flags().BoolVar(&dumpConfig.SkipCACerts, "skip-ca-certificates", false, "do not diff CA certificates.") + diffCmd.Flags().BoolVar(&diffJSONOutput, "json-output", + false, "generate command execution report in a JSON format") addSilenceEventsFlag(diffCmd.Flags()) return diffCmd } diff --git a/cmd/file.go b/cmd/file.go new file mode 100644 index 000000000..71b767f4a --- /dev/null +++ b/cmd/file.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func newAddFileCmd() *cobra.Command { + addFileCmd := &cobra.Command{ + Use: "file [sub-command]...", + Short: "Subcommand to host the decK file manipulation operations", + Long: `Subcommand to host the decK file manipulation operations.`, + } + + return addFileCmd +} diff --git a/cmd/file_addplugins.go b/cmd/file_addplugins.go new file mode 100644 index 000000000..b24956b25 --- /dev/null +++ b/cmd/file_addplugins.go @@ -0,0 +1,161 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/jsonbasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/plugins" + "github.com/spf13/cobra" +) + +var ( + cmdAddPluginsOverwrite bool + cmdAddPluginsInputFilename string + cmdAddPluginOutputFilename string + cmdAddPluginOutputFormat string + cmdAddPluginsSelectors []string + cmdAddPluginsStrConfigs []string +) + +// Executes the CLI command "add-plugins" +func executeAddPlugins(cmd *cobra.Command, cfgFiles []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + cmdAddPluginOutputFormat = strings.ToUpper(cmdAddPluginOutputFormat) + + var pluginConfigs []map[string]interface{} + { + for _, strConfig := range cmdAddPluginsStrConfigs { + pluginConfig, err := filebasics.Deserialize([]byte(strConfig)) + if err != nil { + return fmt.Errorf("failed to deserialize plugin config '%s'; %w", strConfig, err) + } + pluginConfigs = append(pluginConfigs, pluginConfig) + } + } + + var pluginFiles []plugins.DeckPluginFile + { + for _, filename := range cfgFiles { + var file plugins.DeckPluginFile + if err := file.ParseFile(filename); err != nil { + return fmt.Errorf("failed to parse plugin file '%s'; %w", filename, err) + } + pluginFiles = append(pluginFiles, file) + } + } + + // do the work: read/add-plugins/write + jsondata, err := filebasics.DeserializeFile(cmdAddPluginsInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdAddPluginsInputFilename, err) + } + yamlNode := jsonbasics.ConvertToYamlNode(jsondata) + + // apply CLI flags + plugger := plugins.Plugger{} + plugger.SetYamlData(yamlNode) + err = plugger.SetSelectors(cmdAddPluginsSelectors) + if err != nil { + return fmt.Errorf("failed to set selectors; %w", err) + } + err = plugger.AddPlugins(pluginConfigs, cmdAddPluginsOverwrite) + if err != nil { + return fmt.Errorf("failed to add plugins; %w", err) + } + yamlNode = plugger.GetYamlData() + + // apply plugin-files + for i, pluginFile := range pluginFiles { + err = pluginFile.Apply(yamlNode) + if err != nil { + return fmt.Errorf("failed to apply plugin file '%s'; %w", cfgFiles[i], err) + } + } + jsondata = plugger.GetData() + + trackInfo := deckformat.HistoryNewEntry("add-plugins") + trackInfo["input"] = cmdAddPluginsInputFilename + trackInfo["output"] = cmdAddPluginOutputFilename + trackInfo["overwrite"] = cmdAddPluginsOverwrite + if len(pluginConfigs) > 0 { + trackInfo["configs"] = pluginConfigs + } + if len(cfgFiles) > 0 { + trackInfo["pluginfiles"] = cfgFiles + } + trackInfo["selectors"] = cmdAddPluginsSelectors + deckformat.HistoryAppend(jsondata, trackInfo) + + return filebasics.WriteSerializedFile( + cmdAddPluginOutputFilename, + jsondata, + filebasics.OutputFormat(cmdAddPluginOutputFormat)) +} + +// +// +// Define the CLI data for the add-plugins command +// +// + +func newAddPluginsCmd() *cobra.Command { + addPluginsCmd := &cobra.Command{ + Use: "add-plugins [flags] [...plugin-files]", + Short: "Add plugins to objects in a decK file", + Long: `Add plugins to objects in a decK file. + +The plugins are added to all objects that match the selector expressions. If no +selectors are given, the plugins are added to the top-level 'plugins' array. + +The plugin files have the following format (JSON or YAML) and are applied in the +order they are given: + + { "_format_version": "1.0", + "add-plugins": [ + { "selectors": [ + "$..services[*]" + ], + "overwrite": false, + "plugins": [ + { "name": "my-plugin", + "config": { + "my-property": "value" + } + } + ] + } + ] + } +`, + RunE: executeAddPlugins, + Args: cobra.MinimumNArgs(0), + } + + addPluginsCmd.Flags().StringVarP(&cmdAddPluginsInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + addPluginsCmd.Flags().StringArrayVar(&cmdAddPluginsSelectors, "selector", []string{}, + "JSON path expression to select plugin-owning objects to add plugins to.\n"+ + "Defaults to the top-level (selector '$'). Repeat for multiple selectors.") + addPluginsCmd.Flags().StringArrayVar(&cmdAddPluginsStrConfigs, "config", []string{}, + "JSON snippet containing the plugin configuration to add. Repeat to add\n"+ + "multiple plugins.") + addPluginsCmd.Flags().BoolVar(&cmdAddPluginsOverwrite, "overwrite", false, + "Specify this flag to overwrite plugins by the same name if they already\n"+ + "exist in an array. The default behavior is to skip existing plugins.") + addPluginsCmd.Flags().StringVarP(&cmdAddPluginOutputFilename, "output-file", "o", "-", + "Output file to write to. Use - to write to stdout.") + addPluginsCmd.Flags().StringVarP(&cmdAddPluginOutputFormat, "format", "", string(filebasics.OutputFormatYaml), + "Output format: "+string(filebasics.OutputFormatJSON)+" or "+string(filebasics.OutputFormatYaml)) + + return addPluginsCmd +} diff --git a/cmd/file_addtags.go b/cmd/file_addtags.go new file mode 100644 index 000000000..60cbdba73 --- /dev/null +++ b/cmd/file_addtags.go @@ -0,0 +1,89 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/tags" + "github.com/spf13/cobra" +) + +var ( + cmdAddTagsInputFilename string + cmdAddTagsOutputFilename string + cmdAddTagsOutputFormat string + cmdAddTagsSelectors []string +) + +// Executes the CLI command "add-tags" +func executeAddTags(cmd *cobra.Command, tagsToAdd []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + cmdAddTagsOutputFormat = strings.ToUpper(cmdAddTagsOutputFormat) + + // do the work: read/add-tags/write + data, err := filebasics.DeserializeFile(cmdAddTagsInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdAddTagsInputFilename, err) + } + + tagger := tags.Tagger{} + tagger.SetData(data) + err = tagger.SetSelectors(cmdAddTagsSelectors) + if err != nil { + return fmt.Errorf("failed to set selectors; %w", err) + } + err = tagger.AddTags(tagsToAdd) + if err != nil { + return fmt.Errorf("failed to add tags; %w", err) + } + data = tagger.GetData() + + trackInfo := deckformat.HistoryNewEntry("add-tags") + trackInfo["input"] = cmdAddTagsInputFilename + trackInfo["output"] = cmdAddTagsOutputFilename + trackInfo["tags"] = tagsToAdd + trackInfo["selectors"] = cmdAddTagsSelectors + deckformat.HistoryAppend(data, trackInfo) + + return filebasics.WriteSerializedFile(cmdAddTagsOutputFilename, data, filebasics.OutputFormat(cmdAddTagsOutputFormat)) +} + +// +// +// Define the CLI data for the add-tags command +// +// + +func newAddTagsCmd() *cobra.Command { + addTagsCmd := &cobra.Command{ + Use: "add-tags [flags] tag [...tag]", + Short: "Add tags to objects in a decK file", + Long: `Add tags to objects in a decK file. + +The tags are added to all objects that match the selector expressions. If no +selectors are given, all Kong entities are tagged.`, + RunE: executeAddTags, + Args: cobra.MinimumNArgs(1), + } + + addTagsCmd.Flags().StringVarP(&cmdAddTagsInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + addTagsCmd.Flags().StringArrayVar(&cmdAddTagsSelectors, "selector", []string{}, + "JSON path expression to select objects to add tags to.\n"+ + "Defaults to all Kong entities. Repeat for multiple selectors.") + addTagsCmd.Flags().StringVarP(&cmdAddTagsOutputFilename, "output-file", "o", "-", + "Output file to write to. Use - to write to stdout.") + addTagsCmd.Flags().StringVarP(&cmdAddTagsOutputFormat, "format", "", string(filebasics.OutputFormatYaml), + "Output format: "+string(filebasics.OutputFormatJSON)+" or "+string(filebasics.OutputFormatYaml)) + + return addTagsCmd +} diff --git a/cmd/file_convert.go b/cmd/file_convert.go new file mode 100644 index 000000000..60ec957db --- /dev/null +++ b/cmd/file_convert.go @@ -0,0 +1,119 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/kong/deck/convert" + "github.com/kong/deck/cprint" + "github.com/kong/deck/file" + "github.com/kong/deck/utils" + "github.com/spf13/cobra" +) + +var ( + convertCmdSourceFormat string + convertCmdDestinationFormat string // konnect/kong-gateway-3.x/etc + convertCmdInputFile string + convertCmdOutputFile string + convertCmdAssumeYes bool + convertCmdStateFormat string // yaml/json output +) + +func executeConvert(_ *cobra.Command, _ []string) error { + sourceFormat, err := convert.ParseFormat(convertCmdSourceFormat) + if err != nil { + return err + } + destinationFormat, err := convert.ParseFormat(convertCmdDestinationFormat) + if err != nil { + return err + } + + if convertCmdInputFile != "" { + if yes, err := utils.ConfirmFileOverwrite( + convertCmdOutputFile, "", convertCmdAssumeYes, + ); err != nil { + return err + } else if !yes { + return nil + } + + err = convert.Convert( + []string{convertCmdInputFile}, + convertCmdOutputFile, + file.Format(strings.ToUpper(convertCmdStateFormat)), + sourceFormat, + destinationFormat, + false) + if err != nil { + return fmt.Errorf("converting file: %w", err) + } + } else if is2xTo3xConversion() { + path, err := os.Getwd() + if err != nil { + return fmt.Errorf("getting current working directory: %w", err) + } + files, err := utils.ConfigFilesInDir(path) + if err != nil { + return fmt.Errorf("getting files from directory: %w", err) + } + for _, filename := range files { + err = convert.Convert( + []string{filename}, + filename, + file.Format(strings.ToUpper(convertCmdStateFormat)), + sourceFormat, + destinationFormat, + false) + if err != nil { + return fmt.Errorf("converting '%s' file: %w", filename, err) + } + } + } + if convertCmdDestinationFormat == "konnect" { + cprint.UpdatePrintf("Warning: konnect format type was deprecated in v1.12 and it will be removed\n" + + "in a future version. Please use your Kong configuration files with deck .\n" + + "Please see https://docs.konghq.com/konnect/getting-started/import/.\n") + } + return nil +} + +// newConvertCmd represents the convert command +func newConvertCmd() *cobra.Command { + short := "Convert files from one format into another format" + execute := executeConvert + + convertCmd := &cobra.Command{ + Use: "convert", + Short: short, + Long: `The convert command changes configuration files from one format +into another compatible format. For example, a configuration for 'kong-gateway-2.x' +can be converted into a 'kong-gateway-3.x' configuration file.`, + Args: validateNoArgs, + RunE: execute, + } + + sourceFormats := []convert.Format{convert.FormatKongGateway, convert.FormatKongGateway2x} + destinationFormats := []convert.Format{convert.FormatKonnect, convert.FormatKongGateway3x} + convertCmd.Flags().StringVar(&convertCmdSourceFormat, "from", "", + fmt.Sprintf("format of the source file, allowed formats: %v", sourceFormats)) + convertCmd.Flags().StringVar(&convertCmdDestinationFormat, "to", "", + fmt.Sprintf("desired format of the output, allowed formats: %v", destinationFormats)) + convertCmd.Flags().StringVar(&convertCmdInputFile, "input-file", "", + "configuration file to be converted. Use `-` to read from stdin.") + convertCmd.Flags().StringVar(&convertCmdOutputFile, "output-file", "kong.yaml", + "file to write configuration to after conversion. Use `-` to write to stdout.") + convertCmd.Flags().BoolVar(&convertCmdAssumeYes, "yes", + false, "assume `yes` to prompts and run non-interactively.") + convertCmd.Flags().StringVar(&convertCmdStateFormat, "format", + "yaml", "output file format: json or yaml.") + + return convertCmd +} + +func is2xTo3xConversion() bool { + return convertCmdSourceFormat == string(convert.FormatKongGateway2x) && + convertCmdDestinationFormat == string(convert.FormatKongGateway3x) +} diff --git a/cmd/file_kong2kic.go b/cmd/file_kong2kic.go new file mode 100644 index 000000000..49d32bfb6 --- /dev/null +++ b/cmd/file_kong2kic.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/deck/file" + "github.com/kong/go-apiops/logbasics" + "github.com/spf13/cobra" +) + +var ( + cmdKong2KicInputFilename string + cmdKong2KicOutputFilename string + //cmdKong2KicApi string + cmdKong2KicOutputFormat string + cmdKong2KicManifestStyle string +) + +// Executes the CLI command "kong2kic" +func executeKong2Kic(cmd *cobra.Command, _ []string) error { + + var ( + outputContent *file.Content + err error + outputFileFormat file.Format + ) + + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + inputContent, err := file.GetContentFromFiles([]string{cmdKong2KicInputFilename}, false) + if err != nil { + return fmt.Errorf("failed reding input file '%s'; %w", cmdKong2KicInputFilename, err) + } + + outputContent = inputContent.DeepCopy() + if strings.ToUpper(cmdKong2KicOutputFormat) == "JSON" && strings.ToUpper(cmdKong2KicManifestStyle) == "CRD" { + outputFileFormat = file.KIC_JSON_CRD + } else if strings.ToUpper(cmdKong2KicOutputFormat) == "JSON" && strings.ToUpper(cmdKong2KicManifestStyle) == "ANNOTATION" { + outputFileFormat = file.KIC_JSON_ANNOTATION + } else if strings.ToUpper(cmdKong2KicOutputFormat) == "YAML" && strings.ToUpper(cmdKong2KicManifestStyle) == "CRD" { + outputFileFormat = file.KIC_YAML_CRD + } else if strings.ToUpper(cmdKong2KicOutputFormat) == "YAML" && strings.ToUpper(cmdKong2KicManifestStyle) == "ANNOTATION" { + outputFileFormat = file.KIC_YAML_ANNOTATION + } else { + return fmt.Errorf("invalid combination of output format and manifest style") + } + + err = file.WriteContentToFile(outputContent, cmdKong2KicOutputFilename, outputFileFormat) + + if err != nil { + return fmt.Errorf("failed converting Kong to Ingress '%s'; %w", cmdKong2KicInputFilename, err) + } + + return nil +} + +// +// +// Define the CLI data for the openapi2kong command +// +// + +func newKong2KicCmd() *cobra.Command { + kong2KicCmd := &cobra.Command{ + Use: "kong2kic", + Short: "Convert Kong configuration files to Kong Ingress Controller (KIC) manifests", + Long: `Convert Kong configuration files to Kong Ingress Controller (KIC) manifests. + +Manifests can be generated using annotations in Ingress and Service objects (recommended) or +using the KongIngress CRD. Output in YAML or JSON format.`, + RunE: executeKong2Kic, + Args: cobra.NoArgs, + } + + kong2KicCmd.Flags().StringVarP(&cmdKong2KicInputFilename, "input-file", "i", "-", + "Kong spec file to process. Use - to read from stdin.") + kong2KicCmd.Flags().StringVarP(&cmdKong2KicOutputFilename, "output-file", "o", "-", + "Output file to write. Use - to write to stdout.") + kong2KicCmd.Flags().StringVar(&cmdKong2KicManifestStyle, "style", "annotation", + "Generate manifests with annotations in Service and Ingress, or using the KongIngress CRD: annotation or crd.") + kong2KicCmd.Flags().StringVarP(&cmdKong2KicOutputFormat, "format", "f", "yaml", + "output file format: json or yaml.") + //kong2KicCmd.Flags().StringVarP(&cmdKong2KicApi, "api", "a", "ingress", "[ingress|gateway]") + + return kong2KicCmd +} diff --git a/cmd/file_listtags.go b/cmd/file_listtags.go new file mode 100644 index 000000000..b94ee2794 --- /dev/null +++ b/cmd/file_listtags.go @@ -0,0 +1,94 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/tags" + "github.com/spf13/cobra" +) + +const PlainOutputFormat = "PLAIN" + +var ( + cmdListTagsInputFilename string + cmdListTagsOutputFilename string + cmdListTagsOutputFormat string + cmdListTagsSelectors []string +) + +// Executes the CLI command "list-tags" +func executeListTags(cmd *cobra.Command, _ []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + cmdListTagsOutputFormat = strings.ToUpper(cmdListTagsOutputFormat) + + // do the work: read/list-tags/write + data, err := filebasics.DeserializeFile(cmdListTagsInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdListTagsInputFilename, err) + } + + tagger := tags.Tagger{} + tagger.SetData(data) + err = tagger.SetSelectors(cmdListTagsSelectors) + if err != nil { + return fmt.Errorf("failed to set selectors; %w", err) + } + list, err := tagger.ListTags() + if err != nil { + return fmt.Errorf("failed to list tags; %w", err) + } + + if cmdListTagsOutputFormat == PlainOutputFormat { + // return as a plain text format, unix style; line separated + result := []byte(strings.Join(list, "\n")) + return filebasics.WriteFile(cmdListTagsOutputFilename, result) + } + // return as yaml/json, create an object containing only a tags-array + result := make(map[string]interface{}) + result["tags"] = list + return filebasics.WriteSerializedFile( + cmdListTagsOutputFilename, + result, + filebasics.OutputFormat(cmdListTagsOutputFormat)) +} + +// +// +// Define the CLI data for the list-tags command +// +// + +func newListTagsCmd() *cobra.Command { + ListTagsCmd := &cobra.Command{ + Use: "list-tags [flags]", + Short: "List current tags from objects in a decK file", + Long: `List current tags from objects in a decK file. + +The tags are collected from all objects that match the selector expressions. If no +selectors are given, all Kong entities will be scanned.`, + RunE: executeListTags, + Args: cobra.NoArgs, + } + + ListTagsCmd.Flags().StringVarP(&cmdListTagsInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + ListTagsCmd.Flags().StringArrayVar(&cmdListTagsSelectors, "selector", []string{}, + "JSON path expression to select objects to scan for tags.\n"+ + "Defaults to all Kong entities. Repeat for multiple selectors.") + ListTagsCmd.Flags().StringVarP(&cmdListTagsOutputFilename, "output-file", "o", "-", + "Output file to write to. Use - to write to stdout.") + ListTagsCmd.Flags().StringVarP(&cmdListTagsOutputFormat, "format", "", PlainOutputFormat, + "Output format: "+string(filebasics.OutputFormatJSON)+", "+string(filebasics.OutputFormatYaml)+ + ", or "+string(PlainOutputFormat)) + + return ListTagsCmd +} diff --git a/cmd/file_merge.go b/cmd/file_merge.go new file mode 100644 index 000000000..ce7736d61 --- /dev/null +++ b/cmd/file_merge.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "log" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/merge" + "github.com/spf13/cobra" +) + +var ( + cmdMergeOutputFilename string + cmdMergeOutputFormat string +) + +// Executes the CLI command "merge" +func executeMerge(cmd *cobra.Command, args []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + // do the work: read/merge + merged, info, err := merge.Files(args) + if err != nil { + return err + } + + historyEntry := deckformat.HistoryNewEntry("merge") + historyEntry["output"] = cmdMergeOutputFilename + historyEntry["files"] = info + deckformat.HistoryClear(merged) + deckformat.HistoryAppend(merged, historyEntry) + + return filebasics.WriteSerializedFile( + cmdMergeOutputFilename, + merged, + filebasics.OutputFormat(cmdMergeOutputFormat)) +} + +// +// +// Define the CLI data for the merge command +// +// + +func newMergeCmd() *cobra.Command { + mergeCmd := &cobra.Command{ + Use: "merge [flags] filename [...filename]", + Short: "Merge multiple decK files into one", + Long: `Merge multiple decK files into one. + +The files can be in either JSON or YAML format. Merges all top-level arrays by +concatenating them. Any other keys are copied. The files are processed in the order +provided. + +Doesn't perform any checks on content, e.g. duplicates, or any validations. + +If the input files are not compatible, returns an error. Compatibility is +determined by the '_transform' and '_format_version' fields.`, + RunE: executeMerge, + Args: cobra.MinimumNArgs(1), + } + + mergeCmd.Flags().StringVarP(&cmdMergeOutputFilename, "output-file", "o", "-", + "Output file to write to. Use - to write to stdout.") + mergeCmd.Flags().StringVarP(&cmdMergeOutputFormat, "format", "", "yaml", "output format: yaml or json") + + return mergeCmd +} diff --git a/cmd/file_openapi2kong.go b/cmd/file_openapi2kong.go new file mode 100644 index 000000000..9f5d6cb87 --- /dev/null +++ b/cmd/file_openapi2kong.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/openapi2kong" + "github.com/spf13/cobra" +) + +var ( + cmdO2KinputFilename string + cmdO2KoutputFilename string + cmdO2KdocName string + cmdO2KoutputFormat string + cmdO2KentityTags []string +) + +// Executes the CLI command "openapi2kong" +func executeOpenapi2Kong(cmd *cobra.Command, _ []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + if len(cmdO2KentityTags) == 0 { + cmdO2KentityTags = nil + } + + cmdO2KoutputFormat = strings.ToUpper(cmdO2KoutputFormat) + + options := openapi2kong.O2kOptions{ + Tags: cmdO2KentityTags, + DocName: cmdO2KdocName, + } + + trackInfo := deckformat.HistoryNewEntry("openapi2kong") + trackInfo["input"] = cmdO2KinputFilename + trackInfo["output"] = cmdO2KoutputFilename + trackInfo["uuid-base"] = cmdO2KdocName + + // do the work: read/convert/write + content, err := filebasics.ReadFile(cmdO2KinputFilename) + if err != nil { + return err + } + result, err := openapi2kong.Convert(content, options) + if err != nil { + return fmt.Errorf("failed converting OpenAPI spec '%s'; %w", cmdO2KinputFilename, err) + } + deckformat.HistoryAppend(result, trackInfo) + return filebasics.WriteSerializedFile(cmdO2KoutputFilename, result, filebasics.OutputFormat(cmdO2KoutputFormat)) +} + +// +// +// Define the CLI data for the openapi2kong command +// +// + +func newOpenapi2KongCmd() *cobra.Command { + openapi2kongCmd := &cobra.Command{ + Use: "openapi2kong", + Short: "Convert OpenAPI files to Kong's decK format", + Long: `Convert OpenAPI files to Kong's decK format. + +The example file at https://github.com/Kong/go-apiops/blob/main/docs/learnservice_oas.yaml +has extensive annotations explaining the conversion process, as well as all supported +custom annotations (x-kong-... directives).`, + RunE: executeOpenapi2Kong, + Args: cobra.NoArgs, + } + + openapi2kongCmd.Flags().StringVarP(&cmdO2KinputFilename, "spec", "s", "-", + "OpenAPI spec file to process. Use - to read from stdin.") + openapi2kongCmd.Flags().StringVarP(&cmdO2KoutputFilename, "output-file", "o", "-", + "Output file to write. Use - to write to stdout.") + openapi2kongCmd.Flags().StringVarP(&cmdO2KoutputFormat, "format", "", "yaml", "output format: yaml or json") + openapi2kongCmd.Flags().StringVarP(&cmdO2KdocName, "uuid-base", "", "", + "The unique base-string for uuid-v5 generation of entity IDs. If omitted,\n"+ + "uses the root-level \"x-kong-name\" directive, or falls back to 'info.title'.)") + openapi2kongCmd.Flags().StringSliceVar(&cmdO2KentityTags, "select-tag", nil, + "Select tags to apply to all entities. If omitted, uses the \"x-kong-tags\"\n"+ + "directive from the file.") + + return openapi2kongCmd +} diff --git a/cmd/file_patch.go b/cmd/file_patch.go new file mode 100644 index 000000000..af99bf55b --- /dev/null +++ b/cmd/file_patch.go @@ -0,0 +1,177 @@ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/jsonbasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/patch" + "github.com/spf13/cobra" +) + +var ( + cmdPatchInputFilename string + cmdPatchOutputFilename string + cmdPatchOutputFormat string + cmdPatchValues []string + cmdPatchSelectors []string +) + +// Executes the CLI command "patch" +func executePatch(cmd *cobra.Command, args []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + cmdPatchOutputFormat = strings.ToUpper(cmdPatchOutputFormat) + + var valuesPatch patch.DeckPatch + { + var err error + valuesPatch.SelectorSources = cmdPatchSelectors + valuesPatch.ObjValues, valuesPatch.Remove, err = patch.ValidateValuesFlags(cmdPatchValues) + if err != nil { + return fmt.Errorf("failed parsing '--value' entry; %w", err) + } + } + + patchFiles := make([]patch.DeckPatchFile, 0) + { + for _, filename := range args { + var patchfile patch.DeckPatchFile + err := patchfile.ParseFile(filename) + if err != nil { + return fmt.Errorf("failed to parse '%s': %w", filename, err) + } + patchFiles = append(patchFiles, patchfile) + } + } + + trackInfo := deckformat.HistoryNewEntry("patch") + trackInfo["input"] = cmdPatchInputFilename + trackInfo["output"] = cmdPatchOutputFilename + if (len(valuesPatch.ObjValues) + len(valuesPatch.Remove) + len(valuesPatch.ArrValues)) > 0 { + trackInfo["selector"] = valuesPatch.SelectorSources + } + if len(valuesPatch.ObjValues) != 0 { + trackInfo["values"] = valuesPatch.ObjValues + } + if len(valuesPatch.ArrValues) != 0 { + trackInfo["values"] = valuesPatch.ArrValues + } + if len(valuesPatch.Remove) != 0 { + trackInfo["remove"] = valuesPatch.Remove + } + if len(args) != 0 { + trackInfo["patchfiles"] = args + } + + // do the work; read/patch/write + data, err := filebasics.DeserializeFile(cmdPatchInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdPatchInputFilename, err) + } + deckformat.HistoryAppend(data, trackInfo) // add before patching, so patch can operate on it + + yamlNode := jsonbasics.ConvertToYamlNode(data) + + if (len(valuesPatch.ObjValues) + len(valuesPatch.Remove) + len(valuesPatch.ArrValues)) > 0 { + // apply selector + value flags + logbasics.Debug("applying value-flags") + err = valuesPatch.ApplyToNodes(yamlNode) + if err != nil { + return fmt.Errorf("failed to apply command-line values; %w", err) + } + } + + if len(args) > 0 { + // apply patch files + for i, patchFile := range patchFiles { + logbasics.Debug("applying patch-file", "file", i) + err := patchFile.Apply(yamlNode) + if err != nil { + return fmt.Errorf("failed to apply patch-file '%s'; %w", args[i], err) + } + } + } + + data = jsonbasics.ConvertToJSONobject(yamlNode) + + return filebasics.WriteSerializedFile(cmdPatchOutputFilename, data, filebasics.OutputFormat(cmdPatchOutputFormat)) +} + +// +// +// Define the CLI data for the patch command +// +// + +func newPatchCmd() *cobra.Command { + patchCmd := &cobra.Command{ + Use: "patch [flags] [...patch-files]", + Short: "Apply patches on top of a decK file", + Long: `Apply patches on top of a decK file. + +The input file is read, the patches are applied, and if successful, written +to the output file. The patches can be specified by a '--selector' and one or more +'--value' tags, or via patch files. + +When using '--selector' and '--values', the items are selected by the 'selector', +which is a JSONpath query. From the array of nodes found, only the objects are updated. +The 'values' are applied on each of the JSONObjects returned by the 'selector'. + +Objects: + +The value must be a valid JSON snippet, so use single/double quotes +appropriately. If the value is empty, the field is removed from the object. + +Examples of valid values: + + --selector="$..services[*]" --value="read_timeout:10000" + --selector="$..services[*]" --value='_comment:"comment injected by patching"' + --selector="$..services[*]" --value='_ignore:["ignore1","ignore2"]' + --selector="$..services[*]" --value='_ignore:' --value='_comment:' + + +Patch files have the following format (JSON or YAML) and can contain multiple +patches that are applied in order: + + { "_format_version": "1.0", + "patches": [ + { "selectors": [ + "$..services[*]" + ], + "values": { + "read_timeout": 10000, + "_comment": "comment injected by patching" + }, + "remove": [ "_ignore" ] + } + ] + } + +Arrays: + +If the 'values' object instead is an array, then any arrays returned by the selectors +will get the 'values' appended to them. +`, + RunE: executePatch, + } + + patchCmd.Flags().StringVarP(&cmdPatchInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + patchCmd.Flags().StringVarP(&cmdPatchOutputFilename, "output-file", "o", "-", + "Output file to write. Use - to write to stdout.") + patchCmd.Flags().StringVarP(&cmdPatchOutputFormat, "format", "", "yaml", + "Output format: yaml or json.") + patchCmd.Flags().StringArrayVarP(&cmdPatchSelectors, "selector", "", []string{}, + "json-pointer identifying element to patch. Repeat for multiple selectors.)") + patchCmd.Flags().StringArrayVarP(&cmdPatchValues, "value", "", []string{}, + "A value to set in the selected entry in format. Can be specified multiple times.") + patchCmd.MarkFlagsRequiredTogether("selector", "value") + + return patchCmd +} diff --git a/cmd/file_removetags.go b/cmd/file_removetags.go new file mode 100644 index 000000000..49eb0ff42 --- /dev/null +++ b/cmd/file_removetags.go @@ -0,0 +1,112 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "log" + "strings" + + "github.com/kong/go-apiops/deckformat" + "github.com/kong/go-apiops/filebasics" + "github.com/kong/go-apiops/logbasics" + "github.com/kong/go-apiops/tags" + "github.com/spf13/cobra" +) + +var ( + cmdRemoveTagsKeepEmptyArrays bool + cmdRemoveTagsKeepOnlyTags bool + cmdRemoveTagsInputFilename string + cmdRemoveTagsOutputFilename string + cmdRemoveTagsOutputFormat string + cmdRemoveTagsSelectors []string +) + +// Executes the CLI command "remove-tags" +func executeRemoveTags(cmd *cobra.Command, tagsToRemove []string) error { + verbosity, _ := cmd.Flags().GetInt("verbose") + logbasics.Initialize(log.LstdFlags, verbosity) + + cmdRemoveTagsOutputFormat = strings.ToUpper(cmdRemoveTagsOutputFormat) + + if !cmdRemoveTagsKeepOnlyTags && len(tagsToRemove) == 0 { + return fmt.Errorf("no tags to remove") + } + + // do the work: read/remove-tags/write + data, err := filebasics.DeserializeFile(cmdRemoveTagsInputFilename) + if err != nil { + return fmt.Errorf("failed to read input file '%s'; %w", cmdRemoveTagsInputFilename, err) + } + + tagger := tags.Tagger{} + tagger.SetData(data) + err = tagger.SetSelectors(cmdRemoveTagsSelectors) + if err != nil { + return fmt.Errorf("failed to set selectors; %w", err) + } + if cmdRemoveTagsKeepOnlyTags { + err = tagger.RemoveUnknownTags(tagsToRemove, !cmdRemoveTagsKeepEmptyArrays) + } else { + err = tagger.RemoveTags(tagsToRemove, !cmdRemoveTagsKeepEmptyArrays) + } + if err != nil { + return fmt.Errorf("failed to remove tags; %w", err) + } + data = tagger.GetData() + + trackInfo := deckformat.HistoryNewEntry("remove-tags") + trackInfo["input"] = cmdRemoveTagsInputFilename + trackInfo["output"] = cmdRemoveTagsOutputFilename + trackInfo["tags"] = tagsToRemove + trackInfo["keep-empty-array"] = cmdRemoveTagsKeepEmptyArrays + trackInfo["selectors"] = cmdRemoveTagsSelectors + deckformat.HistoryAppend(data, trackInfo) + + return filebasics.WriteSerializedFile( + cmdRemoveTagsOutputFilename, + data, + filebasics.OutputFormat(cmdRemoveTagsOutputFormat)) +} + +// +// +// Define the CLI data for the remove-tags command +// +// + +func newRemoveTagsCmd() *cobra.Command { + removeTagsCmd := &cobra.Command{ + Use: "remove-tags [flags] tag [...tag]", + Short: "Remove tags from objects in a decK file", + Long: `Remove tags from objects in a decK file. + +The listed tags are removed from all objects that match the selector expressions. +If no selectors are given, all Kong entities are selected.`, + RunE: executeRemoveTags, + Example: "# clear tags 'tag1' and 'tag2' from all services in file 'kong.yml'\n" + + "cat kong.yml | deck file remove-tags --selector='services[*]' tag1 tag2\n" + + "\n" + + "# clear all tags except 'tag1' and 'tag2' from the file 'kong.yml'\n" + + "cat kong.yml | deck file remove-tags --keep-only tag1 tag2", + } + + removeTagsCmd.Flags().BoolVar(&cmdRemoveTagsKeepEmptyArrays, "keep-empty-array", false, + "Keep empty tag arrays in output.") + removeTagsCmd.Flags().BoolVar(&cmdRemoveTagsKeepOnlyTags, "keep-only", false, + "Setting this flag will remove all tags except the ones listed.\n"+ + "If none are listed, all tags will be removed.") + removeTagsCmd.Flags().StringVarP(&cmdRemoveTagsInputFilename, "state", "s", "-", + "decK file to process. Use - to read from stdin.") + removeTagsCmd.Flags().StringArrayVar(&cmdRemoveTagsSelectors, "selector", []string{}, + "JSON path expression to select objects to remove tags from.\n"+ + "Defaults to all Kong entities. Repeat for multiple selectors.") + removeTagsCmd.Flags().StringVarP(&cmdRemoveTagsOutputFilename, "output-file", "o", "-", + "Output file to write. Use - to write to stdout.") + removeTagsCmd.Flags().StringVarP(&cmdRemoveTagsOutputFormat, "format", "", string(filebasics.OutputFormatYaml), + "Output format: "+string(filebasics.OutputFormatJSON)+" or "+string(filebasics.OutputFormatYaml)) + + return removeTagsCmd +} diff --git a/cmd/file_render.go b/cmd/file_render.go new file mode 100644 index 000000000..c5b26b138 --- /dev/null +++ b/cmd/file_render.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "strings" + + "github.com/kong/deck/convert" + "github.com/kong/deck/file" + "github.com/spf13/cobra" +) + +var ( + fileRenderCmdKongStateFile []string + fileRenderCmdKongFileOutput string + fileRenderCmdStateFormat string +) + +func executeFileRenderCmd(_ *cobra.Command, _ []string) error { + return convert.Convert( + fileRenderCmdKongStateFile, + fileRenderCmdKongFileOutput, + file.Format(strings.ToUpper(fileRenderCmdStateFormat)), + convert.FormatDistributed, + convert.FormatKongGateway3x, + true) +} + +func newFileRenderCmd() *cobra.Command { + renderCmd := &cobra.Command{ + Use: "render", + Short: "Render the configuration as Kong declarative config", + Long: ``, + Args: cobra.ArbitraryArgs, + RunE: executeFileRenderCmd, + PreRunE: func(cmd *cobra.Command, args []string) error { + fileRenderCmdKongStateFile = args + if len(fileRenderCmdKongStateFile) == 0 { + fileRenderCmdKongStateFile = []string{"-"} // default to stdin + } + return preRunSilenceEventsFlag() + }, + } + + renderCmd.Flags().StringVarP(&fileRenderCmdKongFileOutput, "output-file", "o", + "-", "file to which to write Kong's configuration."+ + "Use `-` to write to stdout.") + renderCmd.Flags().StringVar(&fileRenderCmdStateFormat, "format", + "yaml", "output file format: json or yaml.") + + return renderCmd +} diff --git a/cmd/konnect_diff.go b/cmd/konnect_diff.go index 42e91e649..429c5eecf 100644 --- a/cmd/konnect_diff.go +++ b/cmd/konnect_diff.go @@ -23,9 +23,6 @@ func newKonnectDiffCmd() *cobra.Command { Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { _ = sendAnalytics("konnect-diff", "", modeKonnect) - if konnectConfig.Address == defaultKonnectURL { - konnectConfig.Address = defaultLegacyKonnectURL - } return syncKonnect(cmd.Context(), konnectDiffCmdKongStateFile, true, konnectDiffCmdParallelism) }, diff --git a/cmd/konnect_dump.go b/cmd/konnect_dump.go index c3ddd7100..65d3f6ded 100644 --- a/cmd/konnect_dump.go +++ b/cmd/konnect_dump.go @@ -38,9 +38,6 @@ func newKonnectDumpCmd() *cobra.Command { } // get Konnect client - if konnectConfig.Address == defaultKonnectURL { - konnectConfig.Address = defaultLegacyKonnectURL - } konnectClient, err := utils.GetKonnectClient(httpClient, konnectConfig) if err != nil { return err diff --git a/cmd/konnect_ping.go b/cmd/konnect_ping.go index 06e6b3e18..31e5608ca 100644 --- a/cmd/konnect_ping.go +++ b/cmd/konnect_ping.go @@ -18,9 +18,6 @@ credentials.` + konnectAlphaState, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { _ = sendAnalytics("konnect-ping", "", modeKonnect) - if konnectConfig.Address == defaultKonnectURL { - konnectConfig.Address = defaultLegacyKonnectURL - } client, err := utils.GetKonnectClient(nil, konnectConfig) if err != nil { return err diff --git a/cmd/konnect_sync.go b/cmd/konnect_sync.go index dae3425ce..93de46fc1 100644 --- a/cmd/konnect_sync.go +++ b/cmd/konnect_sync.go @@ -15,9 +15,6 @@ to get Konnect's state in sync with the input state.` + konnectAlphaState, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { _ = sendAnalytics("konnect-sync", "", modeKonnect) - if konnectConfig.Address == defaultKonnectURL { - konnectConfig.Address = defaultLegacyKonnectURL - } return syncKonnect(cmd.Context(), konnectDiffCmdKongStateFile, false, konnectDiffCmdParallelism) }, diff --git a/cmd/ping.go b/cmd/ping.go index d2a6b0e76..4a59e59dd 100644 --- a/cmd/ping.go +++ b/cmd/ping.go @@ -3,7 +3,6 @@ package cmd import ( "context" "fmt" - "net/url" "github.com/kong/deck/utils" "github.com/spf13/cobra" @@ -47,9 +46,8 @@ func pingKonnect(ctx context.Context) error { if err != nil { return err } - u, _ := url.Parse(konnectConfig.Address) // authenticate with konnect - res, err := authenticate(ctx, konnectClient, u.Host, konnectConfig) + res, err := authenticate(ctx, konnectClient, konnectConfig) if err != nil { return fmt.Errorf("authenticating with Konnect: %w", err) } diff --git a/cmd/reset.go b/cmd/reset.go index f9b7574ce..d0a68780e 100644 --- a/cmd/reset.go +++ b/cmd/reset.go @@ -12,6 +12,7 @@ var ( resetCmdForce bool resetWorkspace string resetAllWorkspaces bool + resetJSONOutput bool ) // newResetCmd represents the reset command @@ -59,8 +60,16 @@ By default, this command will ask for confirmation.`, if err != nil { return fmt.Errorf("reading Kong version: %w", err) } + parsedKongVersion, err := utils.ParseKongVersion(kongVersion) + if err != nil { + return fmt.Errorf("parsing Kong version: %w", err) + } _ = sendAnalytics("reset", kongVersion, mode) + if utils.Kong340Version.LTE(parsedKongVersion) { + dumpConfig.IsConsumerGroupScopedPluginSupported = true + } + var workspaces []string // Kong OSS or default workspace if !resetAllWorkspaces && resetWorkspace == "" { @@ -99,7 +108,7 @@ By default, this command will ask for confirmation.`, if err != nil { return err } - _, err = performDiff(ctx, currentState, targetState, false, 10, 0, wsClient, false) + _, err = performDiff(ctx, currentState, targetState, false, 10, 0, wsClient, false, resetJSONOutput) if err != nil { return err } @@ -128,6 +137,8 @@ By default, this command will ask for confirmation.`, false, "reset only the RBAC resources (Kong Enterprise only).") resetCmd.Flags().BoolVar(&dumpConfig.SkipCACerts, "skip-ca-certificates", false, "do not reset CA certificates.") + resetCmd.Flags().BoolVar(&resetJSONOutput, "json-output", + false, "generate command execution report in a JSON format") return resetCmd } diff --git a/cmd/root.go b/cmd/root.go index a14748744..1fda80d3b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,6 +11,7 @@ import ( "github.com/fatih/color" "github.com/kong/deck/utils" + "github.com/kong/go-apiops/deckformat" homedir "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -102,8 +103,7 @@ It can be used to export, import, or sync entities to Kong.`, rootCmd.PersistentFlags().Int("verbose", 0, "Enable verbose logging levels\n"+ - "Setting this value to 2 outputs all HTTP requests/responses\n"+ - "between decK and Kong.") + "Sets the verbosity level of log output (higher is more verbose).") viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose")) @@ -187,7 +187,7 @@ It can be used to export, import, or sync entities to Kong.`, rootCmd.PersistentFlags().Lookup("konnect-token")) rootCmd.PersistentFlags().String("konnect-token-file", "", - "File containing the Peronsal Access Token to your Konnect account.") + "File containing the Personal Access Token to your Konnect account.") viper.BindPFlag("konnect-token-file", rootCmd.PersistentFlags().Lookup("konnect-token-file")) @@ -214,6 +214,19 @@ It can be used to export, import, or sync entities to Kong.`, rootCmd.AddCommand(newConvertCmd()) rootCmd.AddCommand(newCompletionCmd()) rootCmd.AddCommand(newKonnectCmd()) + { + fileCmd := newAddFileCmd() + rootCmd.AddCommand(fileCmd) + fileCmd.AddCommand(newAddPluginsCmd()) + fileCmd.AddCommand(newAddTagsCmd()) + fileCmd.AddCommand(newListTagsCmd()) + fileCmd.AddCommand(newRemoveTagsCmd()) + fileCmd.AddCommand(newMergeCmd()) + fileCmd.AddCommand(newPatchCmd()) + fileCmd.AddCommand(newOpenapi2KongCmd()) + fileCmd.AddCommand(newFileRenderCmd()) + fileCmd.AddCommand(newKong2KicCmd()) + } return rootCmd } @@ -380,3 +393,8 @@ func extendHeaders(headers []string) []string { headers = append(headers, userAgentHeader) return headers } + +func init() { + // set version and commit hash to report in the go-apiops library + deckformat.ToolVersionSet("decK", VERSION, COMMIT) +} diff --git a/cmd/sync.go b/cmd/sync.go index 856577d21..e8d6d5e1b 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -10,6 +10,7 @@ var ( syncCmdParallelism int syncCmdDBUpdateDelay int syncWorkspace string + syncJSONOutput bool ) // newSyncCmd represents the sync command @@ -24,7 +25,7 @@ to get Kong's state in sync with the input state.`, Args: validateNoArgs, RunE: func(cmd *cobra.Command, args []string) error { return syncMain(cmd.Context(), syncCmdKongStateFile, false, - syncCmdParallelism, syncCmdDBUpdateDelay, syncWorkspace) + syncCmdParallelism, syncCmdDBUpdateDelay, syncWorkspace, syncJSONOutput) }, PreRunE: func(cmd *cobra.Command, args []string) error { if len(syncCmdKongStateFile) == 0 { @@ -62,6 +63,8 @@ to get Kong's state in sync with the input state.`, "See `db_update_propagation` in kong.conf.") syncCmd.Flags().BoolVar(&dumpConfig.SkipCACerts, "skip-ca-certificates", false, "do not sync CA certificates.") + syncCmd.Flags().BoolVar(&syncJSONOutput, "json-output", + false, "generate command execution report in a JSON format") addSilenceEventsFlag(syncCmd.Flags()) return syncCmd } diff --git a/cmd/validate.go b/cmd/validate.go index 2035d9933..d44f4d000 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -19,6 +19,7 @@ var ( validateOnline bool validateWorkspace string validateParallelism int + validateJSONOutput bool ) // newValidateCmd represents the diff command @@ -43,7 +44,7 @@ this command unless --online flag is used. _ = sendAnalytics("validate", "", mode) // read target file // this does json schema validation as well - targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile) + targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile, false) if err != nil { return err } @@ -107,6 +108,8 @@ this command unless --online flag is used. "This takes precedence over _workspace fields in state files.") validateCmd.Flags().IntVar(&validateParallelism, "parallelism", 10, "Maximum number of concurrent requests to Kong.") + validateCmd.Flags().BoolVar(&validateJSONOutput, "json-output", + false, "generate command execution report in a JSON format") if err := ensureGetAllMethods(); err != nil { panic(err.Error()) @@ -139,7 +142,7 @@ func getKongClient(ctx context.Context, targetContent *file.Content) (*kong.Clie workspaceName := validateWorkspace if validateWorkspace != "" { // check if workspace exists - workspaceName := getWorkspaceName(validateWorkspace, targetContent) + workspaceName := getWorkspaceName(validateWorkspace, targetContent, validateJSONOutput) workspaceExists, err := workspaceExists(ctx, rootConfig, workspaceName) if err != nil { return nil, err diff --git a/convert/.gitignore b/convert/.gitignore new file mode 100644 index 000000000..c5bf9d03a --- /dev/null +++ b/convert/.gitignore @@ -0,0 +1 @@ +output.yaml diff --git a/convert/convert.go b/convert/convert.go index 49843efd9..2e1ec0ce1 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -1,11 +1,15 @@ package convert import ( + "context" "fmt" "strings" + "github.com/blang/semver/v4" "github.com/kong/deck/cprint" + "github.com/kong/deck/dump" "github.com/kong/deck/file" + "github.com/kong/deck/state" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" ) @@ -13,6 +17,8 @@ import ( type Format string const ( + // FormatDistributed represents the Deck configuration format. + FormatDistributed Format = "distributed" // FormatKongGateway represents the Kong gateway format. FormatKongGateway Format = "kong-gateway" // FormatKonnect represents the Konnect format. @@ -37,42 +43,61 @@ func ParseFormat(key string) (Format, error) { return FormatKongGateway2x, nil case FormatKongGateway3x: return FormatKongGateway3x, nil + case FormatDistributed: + return FormatDistributed, nil default: return "", fmt.Errorf("invalid format: '%v'", key) } } -func Convert(inputFilename, outputFilename string, from, to Format) error { - var ( - outputContent *file.Content - err error - ) +func Convert( + inputFilenames []string, + outputFilename string, + outputFormat file.Format, + from Format, + to Format, + mockEnvVars bool, +) error { + var outputContent *file.Content - inputContent, err := file.GetContentFromFiles([]string{inputFilename}) + inputContent, err := file.GetContentFromFiles(inputFilenames, mockEnvVars) if err != nil { return err } switch { case from == FormatKongGateway && to == FormatKonnect: + if len(inputFilenames) > 1 { + return fmt.Errorf("only one input file can be provided when converting from Kong to Konnect format") + } outputContent, err = convertKongGatewayToKonnect(inputContent) if err != nil { return err } + case from == FormatKongGateway2x && to == FormatKongGateway3x: - outputContent, err = convertKongGateway2xTo3x(inputContent, inputFilename) + if len(inputFilenames) > 1 { + return fmt.Errorf("only one input file can be provided when converting from Kong 2.x to Kong 3.x format") + } + outputContent, err = convertKongGateway2xTo3x(inputContent, inputFilenames[0]) if err != nil { return err } + + case from == FormatDistributed && to == FormatKongGateway, + from == FormatDistributed && to == FormatKongGateway2x, + from == FormatDistributed && to == FormatKongGateway3x: + outputContent, err = convertDistributedToKong(inputContent, outputFilename, outputFormat, to) + if err != nil { + return err + } + default: return fmt.Errorf("cannot convert from '%s' to '%s' format", from, to) } - err = file.WriteContentToFile(outputContent, outputFilename, file.YAML) - if err != nil { - return err - } - return nil + err = file.WriteContentToFile(outputContent, outputFilename, outputFormat) + return err } func convertKongGateway2xTo3x(input *file.Content, filename string) (*file.Content, error) { @@ -195,3 +220,43 @@ func removeServiceName(service *file.FService) *file.FService { serviceCopy.ID = kong.String(utils.UUID()) return serviceCopy } + +// convertDistributedToKong is used to convert one or many distributed format +// files to create one Kong Gateway declarative config. It also leverages some +// deck features like the defaults/centralized plugin configurations. +func convertDistributedToKong( + targetContent *file.Content, + outputFilename string, + format file.Format, + kongFormat Format, +) (*file.Content, error) { + var version semver.Version + + switch kongFormat { //nolint:exhaustive + case FormatKongGateway, + FormatKongGateway3x: + version = semver.Version{Major: 3, Minor: 0} + case FormatKongGateway2x: + version = semver.Version{Major: 2, Minor: 8} + } + + s, _ := state.NewKongState() + rawState, err := file.Get(context.Background(), targetContent, file.RenderConfig{ + CurrentState: s, + KongVersion: version, + }, dump.Config{}, nil) + if err != nil { + return nil, err + } + targetState, err := state.Get(rawState) + if err != nil { + return nil, err + } + + // file.KongStateToContent calls file.WriteContentToFile + return file.KongStateToContent(targetState, file.WriteConfig{ + Filename: outputFilename, + FileFormat: format, + KongVersion: version.String(), + }) +} diff --git a/convert/convert_test.go b/convert/convert_test.go index ab341dfc4..a025a7e72 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -60,6 +60,7 @@ func TestParseFormat(t *testing.T) { want: "", wantErr: true, }, + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -158,9 +159,12 @@ func zeroOutID(sp file.FServicePackage) file.FServicePackage { func Test_Convert(t *testing.T) { type args struct { inputFilename string + inputFilenames []string outputFilename string fromFormat Format toFormat Format + disableMocks bool + envVars map[string]string expectedOutputFilename string } tests := []struct { @@ -237,21 +241,102 @@ func Test_Convert(t *testing.T) { }, wantErr: false, }, + { + name: "converts from distributed to kong gateway (no deck specific fields)", + args: args{ + inputFilename: "testdata/5/input.yaml", + outputFilename: "testdata/5/output.yaml", + expectedOutputFilename: "testdata/5/output-expected.yaml", + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + }, + wantErr: false, + }, + { + name: "converts from distributed to kong gateway with defaults", + args: args{ + inputFilename: "testdata/6/input.yaml", + outputFilename: "testdata/6/output.yaml", + expectedOutputFilename: "testdata/6/output-expected.yaml", + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + }, + wantErr: false, + }, + { + name: "converts from distributed to kong gateway with multiple files", + args: args{ + inputFilenames: []string{"testdata/7/input-1.yaml", "testdata/7/input-2.yaml"}, + outputFilename: "testdata/7/output.yaml", + expectedOutputFilename: "testdata/7/output-expected.yaml", + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + }, + wantErr: false, + }, + { + name: "converts from distributed to kong gateway with env variables", + args: args{ + inputFilenames: []string{"testdata/8/input.yaml"}, + outputFilename: "testdata/8/output.yaml", + expectedOutputFilename: "testdata/8/output-expected.yaml", + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + disableMocks: true, + envVars: map[string]string{ + "DECK_MOCKBIN_HOST": "mockbin.org", + "DECK_MOCKBIN_ENABLED": "true", + "DECK_WRITE_TIMEOUT": "777", + "DECK_FOO_FLOAT": "666", + }, + }, + wantErr: false, + }, + { + name: "converts from distributed to kong gateway with env variables (mocked)", + args: args{ + inputFilenames: []string{"testdata/9/input.yaml"}, + outputFilename: "testdata/9/output.yaml", + expectedOutputFilename: "testdata/9/output-expected.yaml", + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + disableMocks: false, + }, + wantErr: false, + }, + { + name: "errors from distributed to kong gateway with env variables not set", + args: args{ + inputFilenames: []string{"testdata/9/input.yaml"}, + fromFormat: FormatDistributed, + toFormat: FormatKongGateway, + disableMocks: true, + }, + wantErr: true, + }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := Convert(tt.args.inputFilename, tt.args.outputFilename, tt.args.fromFormat, - tt.args.toFormat) + inputFiles := tt.args.inputFilenames + if tt.args.inputFilename != "" { + inputFiles = []string{tt.args.inputFilename} + } + for k, v := range tt.args.envVars { + t.Setenv(k, v) + } + err := Convert(inputFiles, tt.args.outputFilename, file.YAML, tt.args.fromFormat, + tt.args.toFormat, !tt.args.disableMocks) if (err != nil) != tt.wantErr { t.Errorf("Convert() error = %v, wantErr %v", err, tt.wantErr) } if err == nil { - got, err := file.GetContentFromFiles([]string{tt.args.outputFilename}) + got, err := file.GetContentFromFiles([]string{tt.args.outputFilename}, !tt.args.disableMocks) if err != nil { t.Errorf("failed to read output file: %v", err) } - want, err := file.GetContentFromFiles([]string{tt.args.expectedOutputFilename}) + want, err := file.GetContentFromFiles([]string{tt.args.expectedOutputFilename}, !tt.args.disableMocks) if err != nil { t.Errorf("failed to read output file: %v", err) } @@ -385,4 +470,4 @@ func Test_convertKongGatewayToKonnect(t *testing.T) { } }) } -} +} \ No newline at end of file diff --git a/convert/testdata/3/output.yaml b/convert/testdata/3/output.yaml deleted file mode 100644 index eafd39703..000000000 --- a/convert/testdata/3/output.yaml +++ /dev/null @@ -1,10 +0,0 @@ -_format_version: "3.0" -services: -- host: mockbin.org - name: svc1 - path: /status/200 - routes: - - name: r1 - paths: - - ~/status/\d+ - - ~/code/\d+ diff --git a/convert/testdata/4/output.yaml b/convert/testdata/4/output.yaml deleted file mode 100644 index eafd39703..000000000 --- a/convert/testdata/4/output.yaml +++ /dev/null @@ -1,10 +0,0 @@ -_format_version: "3.0" -services: -- host: mockbin.org - name: svc1 - path: /status/200 - routes: - - name: r1 - paths: - - ~/status/\d+ - - ~/code/\d+ diff --git a/convert/testdata/5/input.yaml b/convert/testdata/5/input.yaml new file mode 100644 index 000000000..fafdb57d5 --- /dev/null +++ b/convert/testdata/5/input.yaml @@ -0,0 +1,35 @@ +_format_version: "3.0" +services: +- name: svc1 + host: mockbin.org + tags: + - team-svc1 + routes: + - name: r1 + https_redirect_status_code: 301 + paths: + - /r1 +- name: svc2 + host: mockbin.org + routes: + - name: r2 + https_redirect_status_code: 301 + paths: + - /r2 +- name: svc3 + host: mockbin.org + port: 80 + routes: + - name: r3 + https_redirect_status_code: 301 + paths: + - /r3 + methods: + - GET +plugins: +- name: prometheus + enabled: true + run_on: first + protocols: + - http + - https diff --git a/convert/testdata/5/output-expected.yaml b/convert/testdata/5/output-expected.yaml new file mode 100644 index 000000000..ecd2702a3 --- /dev/null +++ b/convert/testdata/5/output-expected.yaml @@ -0,0 +1,65 @@ +_format_version: "3.0" +plugins: +- enabled: true + name: prometheus + protocols: + - http + - https + run_on: first +services: +- connect_timeout: 60000 + host: mockbin.org + name: svc1 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r1 + paths: + - /r1 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + tags: + - team-svc1 + write_timeout: 60000 +- connect_timeout: 60000 + host: mockbin.org + name: svc2 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r2 + paths: + - /r2 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + write_timeout: 60000 +- connect_timeout: 60000 + host: mockbin.org + name: svc3 + port: 80 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + methods: + - GET + name: r3 + paths: + - /r3 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + write_timeout: 60000 diff --git a/convert/testdata/6/input.yaml b/convert/testdata/6/input.yaml new file mode 100644 index 000000000..fed85d987 --- /dev/null +++ b/convert/testdata/6/input.yaml @@ -0,0 +1,41 @@ +_format_version: "3.0" +_info: + defaults: + service: + connect_timeout: 30000 + write_timeout: 30000 + route: + protocols: + - https + https_redirect_status_code: 301 +services: +- name: svc1 + host: mockbin.org + tags: + - team-svc1 + routes: + - name: r1 + paths: + - /r1 +- name: svc2 + host: mockbin.org + routes: + - name: r2 + paths: + - /r2 +- name: svc3 + host: mockbin.org + port: 80 + routes: + - name: r3 + paths: + - /r3 + methods: + - GET +plugins: +- name: prometheus + enabled: true + run_on: first + protocols: + - http + - https diff --git a/convert/testdata/6/output-expected.yaml b/convert/testdata/6/output-expected.yaml new file mode 100644 index 000000000..bbd6297c7 --- /dev/null +++ b/convert/testdata/6/output-expected.yaml @@ -0,0 +1,62 @@ +_format_version: "3.0" +plugins: +- enabled: true + name: prometheus + protocols: + - http + - https + run_on: first +services: +- connect_timeout: 30000 + host: mockbin.org + name: svc1 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r1 + paths: + - /r1 + preserve_host: false + protocols: + - https + regex_priority: 0 + strip_path: true + tags: + - team-svc1 + write_timeout: 30000 +- connect_timeout: 30000 + host: mockbin.org + name: svc2 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r2 + paths: + - /r2 + preserve_host: false + protocols: + - https + regex_priority: 0 + strip_path: true + write_timeout: 30000 +- connect_timeout: 30000 + host: mockbin.org + name: svc3 + port: 80 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + methods: + - GET + name: r3 + paths: + - /r3 + preserve_host: false + protocols: + - https + regex_priority: 0 + strip_path: true + write_timeout: 30000 diff --git a/convert/testdata/7/input-1.yaml b/convert/testdata/7/input-1.yaml new file mode 100644 index 000000000..9b867b7ef --- /dev/null +++ b/convert/testdata/7/input-1.yaml @@ -0,0 +1,25 @@ +_format_version: "3.0" +services: +- name: svc1 + host: mockbin.org + tags: + - team-svc1 + routes: + - name: r1 + https_redirect_status_code: 301 + paths: + - /r1 +- name: svc2 + host: mockbin.org + routes: + - name: r2 + https_redirect_status_code: 301 + paths: + - /r2 +plugins: +- name: prometheus + enabled: true + run_on: first + protocols: + - http + - https diff --git a/convert/testdata/7/input-2.yaml b/convert/testdata/7/input-2.yaml new file mode 100644 index 000000000..609ab6d4f --- /dev/null +++ b/convert/testdata/7/input-2.yaml @@ -0,0 +1,12 @@ +_format_version: "3.0" +services: +- name: svc3 + host: mockbin.org + port: 80 + routes: + - name: r3 + https_redirect_status_code: 301 + paths: + - /r3 + methods: + - GET diff --git a/convert/testdata/7/output-expected.yaml b/convert/testdata/7/output-expected.yaml new file mode 100644 index 000000000..ecd2702a3 --- /dev/null +++ b/convert/testdata/7/output-expected.yaml @@ -0,0 +1,65 @@ +_format_version: "3.0" +plugins: +- enabled: true + name: prometheus + protocols: + - http + - https + run_on: first +services: +- connect_timeout: 60000 + host: mockbin.org + name: svc1 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r1 + paths: + - /r1 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + tags: + - team-svc1 + write_timeout: 60000 +- connect_timeout: 60000 + host: mockbin.org + name: svc2 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + name: r2 + paths: + - /r2 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + write_timeout: 60000 +- connect_timeout: 60000 + host: mockbin.org + name: svc3 + port: 80 + protocol: http + read_timeout: 60000 + routes: + - https_redirect_status_code: 301 + methods: + - GET + name: r3 + paths: + - /r3 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + strip_path: true + write_timeout: 60000 diff --git a/convert/testdata/8/input.yaml b/convert/testdata/8/input.yaml new file mode 100644 index 000000000..fbe43b53f --- /dev/null +++ b/convert/testdata/8/input.yaml @@ -0,0 +1,9 @@ +services: +- name: svc1 + host: ${{ env "DECK_MOCKBIN_HOST" }} + enabled: ${{ env "DECK_MOCKBIN_ENABLED" | toBool }} + write_timeout: ${{ env "DECK_WRITE_TIMEOUT" | toInt }} +plugins: +- config: + foo: ${{ env "DECK_FOO_FLOAT" | toFloat }} + name: foofloat diff --git a/convert/testdata/8/output-expected.yaml b/convert/testdata/8/output-expected.yaml new file mode 100644 index 000000000..ac8206034 --- /dev/null +++ b/convert/testdata/8/output-expected.yaml @@ -0,0 +1,13 @@ +_format_version: "3.0" +plugins: +- config: + foo: 666 + name: foofloat +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + protocol: http + read_timeout: 60000 + write_timeout: 777 diff --git a/convert/testdata/9/input.yaml b/convert/testdata/9/input.yaml new file mode 100644 index 000000000..fbe43b53f --- /dev/null +++ b/convert/testdata/9/input.yaml @@ -0,0 +1,9 @@ +services: +- name: svc1 + host: ${{ env "DECK_MOCKBIN_HOST" }} + enabled: ${{ env "DECK_MOCKBIN_ENABLED" | toBool }} + write_timeout: ${{ env "DECK_WRITE_TIMEOUT" | toInt }} +plugins: +- config: + foo: ${{ env "DECK_FOO_FLOAT" | toFloat }} + name: foofloat diff --git a/convert/testdata/9/output-expected.yaml b/convert/testdata/9/output-expected.yaml new file mode 100644 index 000000000..8bc7ffd48 --- /dev/null +++ b/convert/testdata/9/output-expected.yaml @@ -0,0 +1,13 @@ +_format_version: "3.0" +plugins: +- config: + foo: 42 + name: foofloat +services: +- connect_timeout: 60000 + enabled: false + host: DECK_MOCKBIN_HOST + name: svc1 + protocol: http + read_timeout: 60000 + write_timeout: 42 diff --git a/cprint/color.go b/cprint/color.go index 7d0a54de3..e5c9d8d2a 100644 --- a/cprint/color.go +++ b/cprint/color.go @@ -54,6 +54,7 @@ var ( createPrintln = color.New(color.FgGreen).PrintlnFunc() deletePrintln = color.New(color.FgRed).PrintlnFunc() updatePrintln = color.New(color.FgYellow).PrintlnFunc() + bluePrintln = color.New(color.BgBlue).PrintlnFunc() // CreatePrintln is fmt.Println with red as foreground color. CreatePrintln = func(a ...interface{}) { @@ -69,4 +70,8 @@ var ( UpdatePrintln = func(a ...interface{}) { conditionalPrintln(updatePrintln, a...) } + + BluePrintLn = func(a ...interface{}) { + conditionalPrintln(bluePrintln, a...) + } ) diff --git a/diff/diff.go b/diff/diff.go index 85ce902b2..b26eec8f0 100644 --- a/diff/diff.go +++ b/diff/diff.go @@ -19,6 +19,32 @@ import ( "github.com/kong/go-kong/kong" ) +type EntityState struct { + Name string `json:"name"` + Kind string `json:"kind"` + Body any `json:"body"` +} + +type Summary struct { + Creating int32 `json:"creating"` + Updating int32 `json:"updating"` + Deleting int32 `json:"deleting"` + Total int32 `json:"total"` +} + +type JSONOutputObject struct { + Changes EntityChanges `json:"changes"` + Summary Summary `json:"summary"` + Warnings []string `json:"warnings"` + Errors []string `json:"errors"` +} + +type EntityChanges struct { + Creating []EntityState `json:"creating"` + Updating []EntityState `json:"updating"` + Deleting []EntityState `json:"deleting"` +} + var errEnqueueFailed = errors.New("failed to queue event") func defaultBackOff() backoff.BackOff { @@ -438,13 +464,15 @@ func generateDiffString(e crud.Event, isDelete bool, noMaskValues bool) (string, return "", err } if !noMaskValues { - diffString = maskEnvVarValue(diffString) + diffString = MaskEnvVarValue(diffString) } return diffString, err } // Solve generates a diff and walks the graph. -func (sc *Syncer) Solve(ctx context.Context, parallelism int, dry bool) (Stats, []error) { +func (sc *Syncer) Solve(ctx context.Context, parallelism int, dry bool, isJSONOut bool) (Stats, + []error, EntityChanges, +) { stats := Stats{ CreateOps: &utils.AtomicInt32Counter{}, UpdateOps: &utils.AtomicInt32Counter{}, @@ -461,22 +489,49 @@ func (sc *Syncer) Solve(ctx context.Context, parallelism int, dry bool) (Stats, } } + output := EntityChanges{ + Creating: []EntityState{}, + Updating: []EntityState{}, + Deleting: []EntityState{}, + } + errs := sc.Run(ctx, parallelism, func(e crud.Event) (crud.Arg, error) { var err error var result crud.Arg c := e.Obj.(state.ConsoleString) + objDiff := map[string]interface{}{ + "old": e.OldObj, + "new": e.Obj, + } + item := EntityState{ + Body: objDiff, + Name: c.Console(), + Kind: string(e.Kind), + } switch e.Op { case crud.Create: - sc.createPrintln("creating", e.Kind, c.Console()) + if isJSONOut { + output.Creating = append(output.Creating, item) + } else { + sc.createPrintln("creating", e.Kind, c.Console()) + } case crud.Update: diffString, err := generateDiffString(e, false, sc.noMaskValues) if err != nil { return nil, err } - sc.updatePrintln("updating", e.Kind, c.Console(), diffString) + if isJSONOut { + output.Updating = append(output.Updating, item) + } else { + sc.updatePrintln("updating", e.Kind, c.Console(), diffString) + } case crud.Delete: - sc.deletePrintln("deleting", e.Kind, c.Console()) + if isJSONOut { + output.Deleting = append(output.Deleting, item) + } else { + sc.deletePrintln("deleting", e.Kind, c.Console()) + } default: panic("unknown operation " + e.Op.String()) } @@ -498,5 +553,5 @@ func (sc *Syncer) Solve(ctx context.Context, parallelism int, dry bool) (Stats, return result, nil }) - return stats, errs + return stats, errs, output } diff --git a/diff/diff_helpers.go b/diff/diff_helpers.go index 6bbd0c768..5be1f726b 100644 --- a/diff/diff_helpers.go +++ b/diff/diff_helpers.go @@ -112,7 +112,7 @@ func parseDeckEnvVars() []EnvVar { return parsedEnvVars } -func maskEnvVarValue(diffString string) string { +func MaskEnvVarValue(diffString string) string { for _, envVar := range parseDeckEnvVars() { diffString = strings.Replace(diffString, envVar.Value, "[masked]", -1) } diff --git a/diff/diff_helpers_test.go b/diff/diff_helpers_test.go index 1088e4e5d..82f26c14f 100644 --- a/diff/diff_helpers_test.go +++ b/diff/diff_helpers_test.go @@ -168,7 +168,7 @@ func Test_MaskEnvVarsValues(t *testing.T) { for k, v := range tt.envVars { t.Setenv(k, v) } - if got := maskEnvVarValue(tt.args); got != tt.want { + if got := MaskEnvVarValue(tt.args); got != tt.want { t.Errorf("maskEnvVarValue() = %v\nwant %v", got, tt.want) } }) diff --git a/dump/dump.go b/dump/dump.go index 51fa2e7d7..1ff4c96b2 100644 --- a/dump/dump.go +++ b/dump/dump.go @@ -6,7 +6,6 @@ import ( "fmt" "net/http" - "github.com/kong/deck/konnect" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" "golang.org/x/sync/errgroup" @@ -31,6 +30,9 @@ type Config struct { // KonnectRuntimeGroup KonnectRuntimeGroup string + + // IsConsumerGroupScopedPluginSupported + IsConsumerGroupScopedPluginSupported bool } func deduplicate(stringSlice []string) []string { @@ -71,15 +73,7 @@ func getConsumerGroupsConfiguration(ctx context.Context, group *errgroup.Group, client *kong.Client, config Config, state *utils.KongRawState, ) { group.Go(func() error { - var ( - err error - consumerGroups []*kong.ConsumerGroupObject - ) - if config.KonnectRuntimeGroup != "" { - consumerGroups, err = GetAllKonnectConsumerGroups(ctx, client, config.SelectorTags) - } else { - consumerGroups, err = GetAllConsumerGroups(ctx, client, config.SelectorTags) - } + consumerGroups, err := GetAllConsumerGroups(ctx, client, config.SelectorTags) if err != nil { if kong.IsNotFoundErr(err) || kong.IsForbiddenErr(err) { return nil @@ -205,6 +199,7 @@ func getProxyConfiguration(ctx context.Context, group *errgroup.Group, plugins = excludeKonnectManagedPlugins(plugins) if config.SkipConsumers { plugins = excludeConsumersPlugins(plugins) + plugins = excludeConsumerGroupsPlugins(plugins) } state.Plugins = plugins return nil @@ -528,29 +523,6 @@ func GetAllUpstreams(ctx context.Context, return upstreams, nil } -// GetAllConsumerGroups queries Konnect for all the ConsumerGroups using client. -func GetAllKonnectConsumerGroups(ctx context.Context, - client *kong.Client, tags []string, -) ([]*kong.ConsumerGroupObject, error) { - var consumerGroupObjects []*kong.ConsumerGroupObject - opt := newOpt(tags) - cgs, err := konnect.ListAllConsumerGroups(ctx, client, opt.Tags) - if err != nil { - return nil, err - } - if err := ctx.Err(); err != nil { - return nil, err - } - for _, cg := range cgs { - r, err := konnect.GetConsumerGroupObject(ctx, client, cg.ID) - if err != nil { - return nil, err - } - consumerGroupObjects = append(consumerGroupObjects, r) - } - return consumerGroupObjects, nil -} - // GetAllConsumerGroups queries Kong for all the ConsumerGroups using client. func GetAllConsumerGroups(ctx context.Context, client *kong.Client, tags []string, @@ -902,3 +874,15 @@ func excludeConsumersPlugins(plugins []*kong.Plugin) []*kong.Plugin { } return filtered } + +// excludeConsumerGroupsPlugins filter out consumer-groups plugins +func excludeConsumerGroupsPlugins(plugins []*kong.Plugin) []*kong.Plugin { + var filtered []*kong.Plugin + for _, p := range plugins { + if p.ConsumerGroup != nil && !utils.Empty(p.ConsumerGroup.ID) { + continue + } + filtered = append(filtered, p) + } + return filtered +} diff --git a/file/builder.go b/file/builder.go index a0286e215..7bbd52a2a 100644 --- a/file/builder.go +++ b/file/builder.go @@ -12,6 +12,8 @@ import ( "github.com/kong/go-kong/kong" ) +const ratelimitingAdvancedPluginName = "rate-limiting-advanced" + type stateBuilder struct { targetContent *Content rawState *utils.KongRawState @@ -35,6 +37,8 @@ type stateBuilder struct { checkRoutePaths bool + isConsumerGroupScopedPluginSupported bool + err error } @@ -69,6 +73,10 @@ func (b *stateBuilder) build() (*utils.KongRawState, *utils.KonnectRawState, err b.checkRoutePaths = true } + if utils.Kong340Version.LTE(b.kongVersion) || b.isKonnect { + b.isConsumerGroupScopedPluginSupported = true + } + // build b.certificates() if !b.skipCACerts { @@ -92,6 +100,46 @@ func (b *stateBuilder) build() (*utils.KongRawState, *utils.KonnectRawState, err return b.rawState, b.konnectRawState, nil } +func (b *stateBuilder) ingestConsumerGroupScopedPlugins(cg FConsumerGroupObject) error { + var plugins []FPlugin + for _, plugin := range cg.Plugins { + plugin.ConsumerGroup = utils.GetConsumerGroupReference(cg.ConsumerGroup) + plugins = append(plugins, FPlugin{ + Plugin: kong.Plugin{ + ID: plugin.ID, + Name: plugin.Name, + Config: plugin.Config, + ConsumerGroup: &kong.ConsumerGroup{ + ID: cg.ID, + }, + }, + }) + } + return b.ingestPlugins(plugins) +} + +func (b *stateBuilder) addConsumerGroupPlugins( + cg FConsumerGroupObject, cgo *kong.ConsumerGroupObject, +) error { + for _, plugin := range cg.Plugins { + if utils.Empty(plugin.ID) { + current, err := b.currentState.ConsumerGroupPlugins.Get( + *plugin.Name, *cg.ConsumerGroup.ID, + ) + if errors.Is(err, state.ErrNotFound) { + plugin.ID = uuid() + } else if err != nil { + return err + } else { + plugin.ID = kong.String(*current.ID) + } + } + b.defaulter.MustSet(plugin) + cgo.Plugins = append(cgo.Plugins, plugin) + } + return nil +} + func (b *stateBuilder) consumerGroups() { if b.err != nil { return @@ -116,22 +164,29 @@ func (b *stateBuilder) consumerGroups() { ConsumerGroup: &cg.ConsumerGroup, } - for _, plugin := range cg.Plugins { - if utils.Empty(plugin.ID) { - current, err := b.currentState.ConsumerGroupPlugins.Get( - *plugin.Name, *cg.ConsumerGroup.ID, - ) - if errors.Is(err, state.ErrNotFound) { - plugin.ID = uuid() - } else if err != nil { - b.err = err - return - } else { - plugin.ID = kong.String(*current.ID) - } + err := b.intermediate.ConsumerGroups.Add(state.ConsumerGroup{ConsumerGroup: cg.ConsumerGroup}) + if err != nil { + b.err = err + return + } + + // Plugins and Consumer Groups can be handled in two ways: + // 1. directly in the ConsumerGroup object + // 2. by scoping the plugin to the ConsumerGroup (Kong >= 3.4.0) + // + // The first method is deprecated and will be removed in the future, but + // we still need to support it for now. The isConsumerGroupScopedPluginSupported + // flag is used to determine which method to use based on the Kong version. + if b.isConsumerGroupScopedPluginSupported { + if err := b.ingestConsumerGroupScopedPlugins(cg); err != nil { + b.err = err + return + } + } else { + if err := b.addConsumerGroupPlugins(cg, &cgo); err != nil { + b.err = err + return } - b.defaulter.MustSet(plugin) - cgo.Plugins = append(cgo.Plugins, plugin) } b.rawState.ConsumerGroups = append(b.rawState.ConsumerGroups, &cgo) } @@ -234,10 +289,16 @@ func (b *stateBuilder) consumers() { for _, c := range b.targetContent.Consumers { c := c if utils.Empty(c.ID) { - consumer, err := b.currentState.Consumers.Get(*c.Username) - if errors.Is(err, state.ErrNotFound) { + var ( + consumer *state.Consumer + err error + ) + if c.Username != nil { + consumer, err = b.currentState.Consumers.GetByIDOrUsername(*c.Username) + } + if errors.Is(err, state.ErrNotFound) || consumer == nil { if c.CustomID != nil { - consumer, err = b.currentState.Consumers.Get(*c.CustomID) + consumer, err = b.currentState.Consumers.GetByCustomID(*c.CustomID) if err == nil { c.ID = kong.String(*consumer.ID) } @@ -844,7 +905,7 @@ func (b *stateBuilder) plugins() { for _, p := range b.targetContent.Plugins { p := p if p.Consumer != nil && !utils.Empty(p.Consumer.ID) { - c, err := b.intermediate.Consumers.Get(*p.Consumer.ID) + c, err := b.intermediate.Consumers.GetByIDOrUsername(*p.Consumer.ID) if errors.Is(err, state.ErrNotFound) { b.err = fmt.Errorf("consumer %v for plugin %v: %w", p.Consumer.FriendlyName(), *p.Name, err) @@ -882,6 +943,23 @@ func (b *stateBuilder) plugins() { } p.Route = utils.GetRouteReference(r.Route) } + if p.ConsumerGroup != nil && !utils.Empty(p.ConsumerGroup.ID) { + cg, err := b.intermediate.ConsumerGroups.Get(*p.ConsumerGroup.ID) + if errors.Is(err, state.ErrNotFound) { + b.err = fmt.Errorf("consumer-group %v for plugin %v: %w", + p.ConsumerGroup.FriendlyName(), *p.Name, err) + return + } else if err != nil { + b.err = err + return + } + p.ConsumerGroup = utils.GetConsumerGroupReference(cg.ConsumerGroup) + } + + if err := b.validatePlugin(p); err != nil { + b.err = err + return + } plugins = append(plugins, p) } if err := b.ingestPlugins(plugins); err != nil { @@ -890,6 +968,29 @@ func (b *stateBuilder) plugins() { } } +func (b *stateBuilder) validatePlugin(p FPlugin) error { + if b.isConsumerGroupScopedPluginSupported && *p.Name == ratelimitingAdvancedPluginName { + // check if deprecated consumer-groups configuration is present in the config + var consumerGroupsFound bool + if groups, ok := p.Config["consumer_groups"]; ok { + // if groups is an array of length > 0, then consumer_groups is set + if groupsArray, ok := groups.([]interface{}); ok && len(groupsArray) > 0 { + consumerGroupsFound = true + } + } + var enforceConsumerGroupsFound bool + if enforceConsumerGroups, ok := p.Config["enforce_consumer_groups"]; ok { + if enforceConsumerGroupsBool, ok := enforceConsumerGroups.(bool); ok && enforceConsumerGroupsBool { + enforceConsumerGroupsFound = true + } + } + if consumerGroupsFound || enforceConsumerGroupsFound { + return utils.ErrorConsumerGroupUpgrade + } + } + return nil +} + // strip_path schema default value is 'true', but it cannot be set when // protocols include 'grpc' and/or 'grpcs'. When users explicitly set // strip_path to 'true' with grpc/s protocols, deck returns a schema violation error. @@ -997,9 +1098,9 @@ func (b *stateBuilder) ingestPlugins(plugins []FPlugin) error { for _, p := range plugins { p := p if utils.Empty(p.ID) { - cID, rID, sID := pluginRelations(&p.Plugin) + cID, rID, sID, cgID := pluginRelations(&p.Plugin) plugin, err := b.currentState.Plugins.GetByProp(*p.Name, - sID, rID, cID) + sID, rID, cID, cgID) if errors.Is(err, state.ErrNotFound) { p.ID = uuid() } else if err != nil { @@ -1044,7 +1145,7 @@ func (b *stateBuilder) fillPluginConfig(plugin *FPlugin) error { return nil } -func pluginRelations(plugin *kong.Plugin) (cID, rID, sID string) { +func pluginRelations(plugin *kong.Plugin) (cID, rID, sID, cgID string) { if plugin.Consumer != nil && !utils.Empty(plugin.Consumer.ID) { cID = *plugin.Consumer.ID } @@ -1054,6 +1155,9 @@ func pluginRelations(plugin *kong.Plugin) (cID, rID, sID string) { if plugin.Service != nil && !utils.Empty(plugin.Service.ID) { sID = *plugin.Service.ID } + if plugin.ConsumerGroup != nil && !utils.Empty(plugin.ConsumerGroup.ID) { + cgID = *plugin.ConsumerGroup.ID + } return } diff --git a/file/builder_test.go b/file/builder_test.go index 0d3557786..1b79c2886 100644 --- a/file/builder_test.go +++ b/file/builder_test.go @@ -293,6 +293,9 @@ func existingPluginState() *state.KongState { Route: &kong.Route{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("69ed4618-a653-4b54-8bb6-dc33bd6fe048"), + }, }, }) return s @@ -751,6 +754,9 @@ func Test_stateBuilder_ingestPlugins(t *testing.T) { Route: &kong.Route{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("69ed4618-a653-4b54-8bb6-dc33bd6fe048"), + }, }, }, }, @@ -780,6 +786,9 @@ func Test_stateBuilder_ingestPlugins(t *testing.T) { Route: &kong.Route{ ID: kong.String("700bc504-b2b1-4abd-bd38-cec92779659e"), }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("69ed4618-a653-4b54-8bb6-dc33bd6fe048"), + }, Config: kong.Configuration{}, }, }, @@ -805,11 +814,12 @@ func Test_pluginRelations(t *testing.T) { plugin *kong.Plugin } tests := []struct { - name string - args args - wantCID string - wantRID string - wantSID string + name string + args args + wantCID string + wantRID string + wantSID string + wantCGID string }{ { args: args{ @@ -817,9 +827,10 @@ func Test_pluginRelations(t *testing.T) { Name: kong.String("foo"), }, }, - wantCID: "", - wantRID: "", - wantSID: "", + wantCID: "", + wantRID: "", + wantSID: "", + wantCGID: "", }, { args: args{ @@ -834,16 +845,20 @@ func Test_pluginRelations(t *testing.T) { Service: &kong.Service{ ID: kong.String("sID"), }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("cgID"), + }, }, }, - wantCID: "cID", - wantRID: "rID", - wantSID: "sID", + wantCID: "cID", + wantRID: "rID", + wantSID: "sID", + wantCGID: "cgID", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotCID, gotRID, gotSID := pluginRelations(tt.args.plugin) + gotCID, gotRID, gotSID, gotCGID := pluginRelations(tt.args.plugin) if gotCID != tt.wantCID { t.Errorf("pluginRelations() gotCID = %v, want %v", gotCID, tt.wantCID) } @@ -853,6 +868,9 @@ func Test_pluginRelations(t *testing.T) { if gotSID != tt.wantSID { t.Errorf("pluginRelations() gotSID = %v, want %v", gotSID, tt.wantSID) } + if gotCGID != tt.wantCGID { + t.Errorf("pluginRelations() gotCGID = %v, want %v", gotCGID, tt.wantCGID) + } }) } } diff --git a/file/codegen/main.go b/file/codegen/main.go index be480d51f..b9d5cacf0 100644 --- a/file/codegen/main.go +++ b/file/codegen/main.go @@ -22,12 +22,15 @@ var ( }, } - anyOfUsernameOrID = []*jsonschema.Type{ + // consumers + anyOfUsernameOrCustomID = []*jsonschema.Type{ { - Required: []string{"username"}, + Description: "at least one of custom_id or username must be set", + Required: []string{"username"}, }, { - Required: []string{"id"}, + Description: "at least one of custom_id or username must be set", + Required: []string{"custom_id"}, }, } ) @@ -52,7 +55,7 @@ func main() { schema.Definitions["FRoute"].AnyOf = anyOfNameOrID - schema.Definitions["FConsumer"].AnyOf = anyOfUsernameOrID + schema.Definitions["FConsumer"].AnyOf = anyOfUsernameOrCustomID schema.Definitions["FUpstream"].Required = []string{"name"} @@ -97,6 +100,7 @@ func main() { schema.Definitions["FPlugin"].Properties["consumer"] = stringType schema.Definitions["FPlugin"].Properties["service"] = stringType schema.Definitions["FPlugin"].Properties["route"] = stringType + schema.Definitions["FPlugin"].Properties["consumer_group"] = stringType schema.Definitions["FService"].Properties["client_certificate"] = stringType diff --git a/file/kong2kic.go b/file/kong2kic.go new file mode 100644 index 000000000..e4ff18322 --- /dev/null +++ b/file/kong2kic.go @@ -0,0 +1,1158 @@ +package file + +import ( + "encoding/json" + "log" + "strconv" + "strings" + + kicv1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1" + kicv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1" + k8scorev1 "k8s.io/api/core/v1" + k8snetv1 "k8s.io/api/networking/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// Builder + Director design pattern to create kubernetes manifests based on: +// 1 - Kong custom resource definitions +// 2 - Kong annotations +// 3 - Kubernetes Gateway spec +type IBuilder interface { + buildServices(*Content) + buildRoutes(*Content) + buildGlobalPlugins(*Content) + buildConsumers(*Content) + buildConsumerGroups(*Content) + getContent() *KICContent +} + +const ( + CUSTOM_RESOURCE = "CUSTOM_RESOURCE" + ANNOTATIONS = "ANNOTATIONS" + GATEWAY = "GATEWAY" +) + +func getBuilder(builderType string) IBuilder { + if builderType == CUSTOM_RESOURCE { + return newCustomResourceBuilder() + } + + if builderType == ANNOTATIONS { + return newAnnotationsBuilder() + } + + if builderType == GATEWAY { + // TODO: implement gateway builder + } + return nil +} + +type CustomResourceBuilder struct { + kicContent *KICContent +} + +func newCustomResourceBuilder() *CustomResourceBuilder { + return &CustomResourceBuilder{ + kicContent: &KICContent{}, + } +} + +func (b *CustomResourceBuilder) buildServices(content *Content) { + err := populateKICServicesWithCustomResources(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *CustomResourceBuilder) buildRoutes(content *Content) { + err := populateKICIngressesWithCustomResources(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *CustomResourceBuilder) buildGlobalPlugins(content *Content) { + err := populateKICKongClusterPlugins(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *CustomResourceBuilder) buildConsumers(content *Content) { + err := populateKICConsumers(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *CustomResourceBuilder) buildConsumerGroups(content *Content) { + err := populateKICConsumerGroups(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *CustomResourceBuilder) getContent() *KICContent { + return b.kicContent +} + +type AnnotationsBuilder struct { + kicContent *KICContent +} + +func newAnnotationsBuilder() *AnnotationsBuilder { + return &AnnotationsBuilder{ + kicContent: &KICContent{}, + } +} + +func (b *AnnotationsBuilder) buildServices(content *Content) { + err := populateKICServicesWithAnnotations(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *AnnotationsBuilder) buildRoutes(content *Content) { + err := populateKICIngressesWithAnnotations(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *AnnotationsBuilder) buildGlobalPlugins(content *Content) { + err := populateKICKongClusterPlugins(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *AnnotationsBuilder) buildConsumers(content *Content) { + err := populateKICConsumers(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *AnnotationsBuilder) buildConsumerGroups(content *Content) { + err := populateKICConsumerGroups(content, b.kicContent) + if err != nil { + log.Fatal(err) + } +} + +func (b *AnnotationsBuilder) getContent() *KICContent { + return b.kicContent +} + +type Director struct { + builder IBuilder +} + +func newDirector(builder IBuilder) *Director { + return &Director{ + builder: builder, + } +} + +func (d *Director) buildManifests(content *Content) *KICContent { + d.builder.buildServices(content) + d.builder.buildRoutes(content) + d.builder.buildGlobalPlugins(content) + d.builder.buildConsumers(content) + d.builder.buildConsumerGroups(content) + return d.builder.getContent() +} + +//////////////////// +/// End of Builder + Director +//////////////////// + +func MarshalKongToKICYaml(content *Content, builderType string) ([]byte, error) { + kicContent := convertKongToKIC(content, builderType) + return kicContent.marshalKICContentToYaml() +} + +func MarshalKongToKICJson(content *Content, builderType string) ([]byte, error) { + kicContent := convertKongToKIC(content, builderType) + return kicContent.marshalKICContentToJson() + +} + +func convertKongToKIC(content *Content, builderType string) *KICContent { + builder := getBuilder(builderType) + director := newDirector(builder) + return director.buildManifests(content) +} + +///// +// Functions valid for both custom resources and annotations based manifests +///// + +func populateKICKongClusterPlugins(content *Content, file *KICContent) error { + + // Global Plugins map to KongClusterPlugins + // iterate content.Plugins and copy them into kicv1.KongPlugin manifests + // add the kicv1.KongPlugin to the KICContent.KongClusterPlugins slice + for _, plugin := range content.Plugins { + var kongPlugin kicv1.KongClusterPlugin + kongPlugin.APIVersion = "configuration.konghq.com/v1" + kongPlugin.Kind = "KongClusterPlugin" + if plugin.InstanceName != nil { + kongPlugin.ObjectMeta.Name = *plugin.InstanceName + } + kongPlugin.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + if plugin.Name != nil { + kongPlugin.PluginName = *plugin.Name + } + + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + var configJSON apiextensionsv1.JSON + var err error + configJSON.Raw, err = json.Marshal(plugin.Config) + if err != nil { + return err + } + kongPlugin.Config = configJSON + file.KongClusterPlugins = append(file.KongClusterPlugins, kongPlugin) + } + return nil +} + +func populateKICConsumers(content *Content, file *KICContent) error { + // Iterate Kong Consumers and copy them into KongConsumer + for _, consumer := range content.Consumers { + var kongConsumer kicv1.KongConsumer + kongConsumer.APIVersion = "configuration.konghq.com/v1" + kongConsumer.Kind = "KongConsumer" + kongConsumer.ObjectMeta.Name = *consumer.Username + kongConsumer.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + kongConsumer.Username = *consumer.Username + if consumer.CustomID != nil { + kongConsumer.CustomID = *consumer.CustomID + } + + populateKICKeyAuthSecrets(&consumer, &kongConsumer, file) + populateKICHMACSecrets(&consumer, &kongConsumer, file) + populateKICJWTAuthSecrets(&consumer, &kongConsumer, file) + populateKICBasicAuthSecrets(&consumer, &kongConsumer, file) + populateKICOAuth2CredSecrets(&consumer, &kongConsumer, file) + populateKICACLGroupSecrets(&consumer, &kongConsumer, file) + populateKICMTLSAuthSecrets(&consumer, &kongConsumer, file) + + // for each consumer.plugin, create a KongPlugin and a plugin annotation in the kongConsumer + // to link the plugin + for _, plugin := range consumer.Plugins { + var kongPlugin kicv1.KongPlugin + kongPlugin.APIVersion = "configuration.konghq.com/v1" + kongPlugin.Kind = "KongPlugin" + if plugin.InstanceName != nil { + kongPlugin.ObjectMeta.Name = *plugin.InstanceName + } + kongPlugin.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + if plugin.Name != nil { + kongPlugin.PluginName = *plugin.Name + } + + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + var configJSON apiextensionsv1.JSON + var err error + configJSON.Raw, err = json.Marshal(plugin.Config) + if err != nil { + return err + } + kongPlugin.Config = configJSON + file.KongPlugins = append(file.KongPlugins, kongPlugin) + + if kongConsumer.ObjectMeta.Annotations["konghq.com/plugins"] == "" { + kongConsumer.ObjectMeta.Annotations["konghq.com/plugins"] = kongPlugin.PluginName + } else { + kongConsumer.ObjectMeta.Annotations["konghq.com/plugins"] = kongConsumer.ObjectMeta.Annotations["konghq.com/plugins"] + "," + kongPlugin.PluginName + } + } + + file.KongConsumers = append(file.KongConsumers, kongConsumer) + } + + return nil +} + +func populateKICMTLSAuthSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.MTLSAuths and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, mtlsAuth := range consumer.MTLSAuths { + var secret k8scorev1.Secret + var secretName = "mtls-auth-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.Type = "Opaque" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "mtls-auth" + + if mtlsAuth.SubjectName != nil { + secret.StringData["subject_name"] = *mtlsAuth.SubjectName + } + + if mtlsAuth.ID != nil { + secret.StringData["id"] = *mtlsAuth.ID + } + + if mtlsAuth.CACertificate != nil && mtlsAuth.CACertificate.Cert != nil { + secret.StringData["ca_certificate"] = *mtlsAuth.CACertificate.Cert + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICACLGroupSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.ACLGroups and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, aclGroup := range consumer.ACLGroups { + var secret k8scorev1.Secret + var secretName = "acl-group-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + + secret.StringData["kongCredType"] = "acl" + if aclGroup.Group != nil { + secret.StringData["group"] = *aclGroup.Group + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICOAuth2CredSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.OAuth2Creds and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, oauth2Cred := range consumer.Oauth2Creds { + var secret k8scorev1.Secret + var secretName = "oauth2cred-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "oauth2" + + if oauth2Cred.Name != nil { + secret.StringData["name"] = *oauth2Cred.Name + } + + if oauth2Cred.ClientID != nil { + secret.StringData["client_id"] = *oauth2Cred.ClientID + } + + if oauth2Cred.ClientSecret != nil { + secret.StringData["client_secret"] = *oauth2Cred.ClientSecret + } + + if oauth2Cred.ClientType != nil { + secret.StringData["client_type"] = *oauth2Cred.ClientType + } + + if oauth2Cred.HashSecret != nil { + secret.StringData["hash_secret"] = strconv.FormatBool(*oauth2Cred.HashSecret) + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICBasicAuthSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.BasicAuths and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, basicAuth := range consumer.BasicAuths { + var secret k8scorev1.Secret + var secretName = "basic-auth-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "basic-auth" + + if basicAuth.Username != nil { + secret.StringData["username"] = *basicAuth.Username + } + if basicAuth.Password != nil { + secret.StringData["password"] = *basicAuth.Password + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICJWTAuthSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.JWTAuths and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, jwtAuth := range consumer.JWTAuths { + var secret k8scorev1.Secret + var secretName = "jwt-auth-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "jwt" + + // only do the following assignments if not null + if jwtAuth.Key != nil { + secret.StringData["key"] = *jwtAuth.Key + } + + if jwtAuth.Algorithm != nil { + secret.StringData["algorithm"] = *jwtAuth.Algorithm + } + + if jwtAuth.RSAPublicKey != nil { + secret.StringData["rsa_public_key"] = *jwtAuth.RSAPublicKey + } + + if jwtAuth.Secret != nil { + secret.StringData["secret"] = *jwtAuth.Secret + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICHMACSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.HMACAuths and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + for _, hmacAuth := range consumer.HMACAuths { + var secret k8scorev1.Secret + var secretName = "hmac-auth-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "hmac-auth" + + if hmacAuth.Username != nil { + secret.StringData["username"] = *hmacAuth.Username + } + + if hmacAuth.Secret != nil { + secret.StringData["secret"] = *hmacAuth.Secret + } + + // add the secret name to the kongConsumer.credentials + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + } +} + +func populateKICKeyAuthSecrets(consumer *FConsumer, kongConsumer *kicv1.KongConsumer, file *KICContent) { + // iterate consumer.KeyAuths and copy them into k8scorev1.Secret, then add them to kicContent.Secrets + // add the secret name to the kongConsumer.credentials + for _, keyAuth := range consumer.KeyAuths { + var secret k8scorev1.Secret + var secretName = "key-auth-" + *consumer.Username + secret.TypeMeta.APIVersion = "v1" + secret.TypeMeta.Kind = "Secret" + secret.ObjectMeta.Name = strings.ToLower(secretName) + secret.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + secret.StringData = make(map[string]string) + secret.StringData["kongCredType"] = "key-auth" + + if keyAuth.Key != nil { + secret.StringData["key"] = *keyAuth.Key + } + + kongConsumer.Credentials = append(kongConsumer.Credentials, secretName) + + file.Secrets = append(file.Secrets, secret) + + } +} + +func populateKICUpstream(content *Content, service *FService, k8sservice *k8scorev1.Service, kicContent *KICContent) { + + // add Kong specific configuration to the k8s service via a KongIngress resource + + if content.Upstreams != nil { + var kongIngress kicv1.KongIngress + kongIngress.APIVersion = "configuration.konghq.com/v1" + kongIngress.Kind = "KongIngress" + kongIngress.ObjectMeta.Name = *service.Name + kongIngress.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + + // add an annotation to the k8sservice to link this kongIngress to it + k8sservice.ObjectMeta.Annotations["konghq.com/override"] = kongIngress.ObjectMeta.Name + + // Find the upstream (if any) whose name matches the service host and copy the upstream + // into a kicv1.KongIngress resource. Append the kicv1.KongIngress to kicContent.KongIngresses. + for _, upstream := range content.Upstreams { + if upstream.Name != nil && strings.EqualFold(*upstream.Name, *service.Host) { + kongIngress.Upstream = &kicv1.KongIngressUpstream{ + HostHeader: upstream.HostHeader, + Algorithm: upstream.Algorithm, + Slots: upstream.Slots, + Healthchecks: upstream.Healthchecks, + HashOn: upstream.HashOn, + HashFallback: upstream.HashFallback, + HashOnHeader: upstream.HashOnHeader, + HashFallbackHeader: upstream.HashFallbackHeader, + HashOnCookie: upstream.HashOnCookie, + HashOnCookiePath: upstream.HashOnCookiePath, + HashOnQueryArg: upstream.HashOnQueryArg, + HashFallbackQueryArg: upstream.HashFallbackQueryArg, + HashOnURICapture: upstream.HashOnURICapture, + HashFallbackURICapture: upstream.HashFallbackURICapture, + } + } + } + kicContent.KongIngresses = append(kicContent.KongIngresses, kongIngress) + } +} + +func addPluginsToService(service FService, k8sService k8scorev1.Service, kicContent *KICContent) error { + for _, plugin := range service.Plugins { + var kongPlugin kicv1.KongPlugin + kongPlugin.APIVersion = "configuration.konghq.com/v1" + kongPlugin.Kind = "KongPlugin" + if plugin.Name != nil { + kongPlugin.ObjectMeta.Name = *plugin.Name + } + kongPlugin.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + kongPlugin.PluginName = *plugin.Name + + var configJSON apiextensionsv1.JSON + var err error + configJSON.Raw, err = json.Marshal(plugin.Config) + if err != nil { + return err + } + kongPlugin.Config = configJSON + + if k8sService.ObjectMeta.Annotations["konghq.com/plugins"] == "" { + k8sService.ObjectMeta.Annotations["konghq.com/plugins"] = kongPlugin.PluginName + } else { + k8sService.ObjectMeta.Annotations["konghq.com/plugins"] = k8sService.ObjectMeta.Annotations["konghq.com/plugins"] + "," + kongPlugin.PluginName + } + + kicContent.KongPlugins = append(kicContent.KongPlugins, kongPlugin) + } + return nil +} + +func addPluginsToRoute(route *FRoute, routeIngresses []k8snetv1.Ingress, kicContent *KICContent) error { + for _, plugin := range route.Plugins { + var kongPlugin kicv1.KongPlugin + kongPlugin.APIVersion = "configuration.konghq.com/v1" + kongPlugin.Kind = "KongPlugin" + if plugin.Name != nil { + kongPlugin.ObjectMeta.Name = *plugin.Name + } + kongPlugin.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + kongPlugin.PluginName = *plugin.Name + + var configJSON apiextensionsv1.JSON + var err error + configJSON.Raw, err = json.Marshal(plugin.Config) + if err != nil { + return err + } + kongPlugin.Config = configJSON + + for _, k8sIngress := range routeIngresses { + if k8sIngress.ObjectMeta.Annotations["konghq.com/plugins"] == "" { + k8sIngress.ObjectMeta.Annotations["konghq.com/plugins"] = kongPlugin.PluginName + } else { + k8sIngress.ObjectMeta.Annotations["konghq.com/plugins"] = k8sIngress.ObjectMeta.Annotations["konghq.com/plugins"] + "," + kongPlugin.PluginName + } + } + + kicContent.KongPlugins = append(kicContent.KongPlugins, kongPlugin) + } + return nil +} + +func fillIngressHostAndPortSection(host *string, service FService, k8sIngress *k8snetv1.Ingress, path *string, pathTypeImplSpecific k8snetv1.PathType) { + if host != nil && service.Port != nil { + k8sIngress.Spec.Rules = append(k8sIngress.Spec.Rules, k8snetv1.IngressRule{ + Host: *host, + IngressRuleValue: k8snetv1.IngressRuleValue{ + HTTP: &k8snetv1.HTTPIngressRuleValue{ + Paths: []k8snetv1.HTTPIngressPath{ + { + Path: *path, + PathType: &pathTypeImplSpecific, + Backend: k8snetv1.IngressBackend{ + Service: &k8snetv1.IngressServiceBackend{ + Name: *service.Name, + Port: k8snetv1.ServiceBackendPort{ + Number: int32(*service.Port), + }, + }, + }, + }, + }, + }, + }, + }) + } else if host == nil && service.Port != nil { + k8sIngress.Spec.Rules = append(k8sIngress.Spec.Rules, k8snetv1.IngressRule{ + IngressRuleValue: k8snetv1.IngressRuleValue{ + HTTP: &k8snetv1.HTTPIngressRuleValue{ + Paths: []k8snetv1.HTTPIngressPath{ + { + Path: *path, + PathType: &pathTypeImplSpecific, + Backend: k8snetv1.IngressBackend{ + Service: &k8snetv1.IngressServiceBackend{ + Name: *service.Name, + Port: k8snetv1.ServiceBackendPort{ + Number: int32(*service.Port), + }, + }, + }, + }, + }, + }, + }, + }) + } else if host != nil && service.Port == nil { + k8sIngress.Spec.Rules = append(k8sIngress.Spec.Rules, k8snetv1.IngressRule{ + Host: *host, + IngressRuleValue: k8snetv1.IngressRuleValue{ + HTTP: &k8snetv1.HTTPIngressRuleValue{ + Paths: []k8snetv1.HTTPIngressPath{ + { + Path: *path, + PathType: &pathTypeImplSpecific, + Backend: k8snetv1.IngressBackend{ + Service: &k8snetv1.IngressServiceBackend{ + Name: *service.Name, + }, + }, + }, + }, + }, + }, + }) + } else { + + k8sIngress.Spec.Rules = append(k8sIngress.Spec.Rules, k8snetv1.IngressRule{ + IngressRuleValue: k8snetv1.IngressRuleValue{ + HTTP: &k8snetv1.HTTPIngressRuleValue{ + Paths: []k8snetv1.HTTPIngressPath{ + { + Path: *path, + PathType: &pathTypeImplSpecific, + Backend: k8snetv1.IngressBackend{ + Service: &k8snetv1.IngressServiceBackend{ + Name: *service.Name, + }, + }, + }, + }, + }, + }, + }) + } +} + +func populateKICConsumerGroups(content *Content, kicContent *KICContent) error { + // iterate over the consumer groups and create a KongConsumerGroup for each one + for _, consumerGroup := range content.ConsumerGroups { + var kongConsumerGroup kicv1beta1.KongConsumerGroup + kongConsumerGroup.APIVersion = "configuration.konghq.com/v1" + kongConsumerGroup.Kind = "KongConsumerGroup" + kongConsumerGroup.ObjectMeta.Name = *consumerGroup.Name + kongConsumerGroup.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + kongConsumerGroup.Name = *consumerGroup.Name + + // Iterate over the consumers in consumerGroup and + // find the KongConsumer with the same username in kicContent.KongConsumers + // and add it to the KongConsumerGroup + for _, consumer := range consumerGroup.Consumers { + for idx, _ := range kicContent.KongConsumers { + if kicContent.KongConsumers[idx].Username == *consumer.Username { + if kicContent.KongConsumers[idx].ConsumerGroups == nil { + kicContent.KongConsumers[idx].ConsumerGroups = make([]string, 0) + } + kicContent.KongConsumers[idx].ConsumerGroups = append(kicContent.KongConsumers[idx].ConsumerGroups, *consumerGroup.Name) + } + } + } + + // for each consumerGroup.plugin, create a KongPlugin and a plugin annotation in the kongConsumerGroup + // to link the plugin + for _, plugin := range consumerGroup.Plugins { + var kongPlugin kicv1.KongPlugin + kongPlugin.APIVersion = "configuration.konghq.com/v1" + kongPlugin.Kind = "KongPlugin" + kongPlugin.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + if plugin.Name != nil { + kongPlugin.PluginName = *plugin.Name + } + + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + var configJSON apiextensionsv1.JSON + var err error + configJSON.Raw, err = json.Marshal(plugin.Config) + if err != nil { + return err + } + kongPlugin.Config = configJSON + kicContent.KongPlugins = append(kicContent.KongPlugins, kongPlugin) + + if kongConsumerGroup.ObjectMeta.Annotations["konghq.com/plugins"] == "" { + kongConsumerGroup.ObjectMeta.Annotations["konghq.com/plugins"] = kongPlugin.PluginName + } else { + kongConsumerGroup.ObjectMeta.Annotations["konghq.com/plugins"] = kongConsumerGroup.ObjectMeta.Annotations["konghq.com/plugins"] + "," + kongPlugin.PluginName + } + } + + kicContent.KongConsumerGroups = append(kicContent.KongConsumerGroups, kongConsumerGroup) + } + + return nil + +} + +///// +// Functions for CUSTOM RESOURCES based manifests +///// + +func populateKICServiceProxyAndUpstreamCustomResources(content *Content, service *FService, k8sservice *k8scorev1.Service, kicContent *KICContent) { + + // add Kong specific configuration to the k8s service via a KongIngress resource + + var kongIngress kicv1.KongIngress + kongIngress.APIVersion = "configuration.konghq.com/v1" + kongIngress.Kind = "KongIngress" + kongIngress.ObjectMeta.Name = *service.Name + kongIngress.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + + // add an annotation to the k8sservice to link this kongIngress to it + k8sservice.ObjectMeta.Annotations["konghq.com/override"] = kongIngress.ObjectMeta.Name + + // proxy attributes from the service to the kongIngress + kongIngress.Proxy = &kicv1.KongIngressService{ + Protocol: service.Protocol, + Path: service.Path, + Retries: service.Retries, + ConnectTimeout: service.ConnectTimeout, + WriteTimeout: service.WriteTimeout, + ReadTimeout: service.ReadTimeout, + } + + // Find the upstream (if any) whose name matches the service host and copy the upstream + // into a kicv1.KongIngress resource. Append the kicv1.KongIngress to kicContent.KongIngresses. + for _, upstream := range content.Upstreams { + if upstream.Name != nil && strings.EqualFold(*upstream.Name, *service.Host) { + kongIngress.Upstream = &kicv1.KongIngressUpstream{ + HostHeader: upstream.HostHeader, + Algorithm: upstream.Algorithm, + Slots: upstream.Slots, + Healthchecks: upstream.Healthchecks, + HashOn: upstream.HashOn, + HashFallback: upstream.HashFallback, + HashOnHeader: upstream.HashOnHeader, + HashFallbackHeader: upstream.HashFallbackHeader, + HashOnCookie: upstream.HashOnCookie, + HashOnCookiePath: upstream.HashOnCookiePath, + HashOnQueryArg: upstream.HashOnQueryArg, + HashFallbackQueryArg: upstream.HashFallbackQueryArg, + HashOnURICapture: upstream.HashOnURICapture, + HashFallbackURICapture: upstream.HashFallbackURICapture, + } + } + } + kicContent.KongIngresses = append(kicContent.KongIngresses, kongIngress) +} + +func populateKICServicesWithCustomResources(content *Content, kicContent *KICContent) error { + + // Iterate Kong Services and create k8s Services, + // then create KongIngress resources for Kong Service + // specific configuration and Upstream data. + // Finally, create KongPlugin resources for each plugin + // associated with the service. + for _, service := range content.Services { + var k8sService k8scorev1.Service + var protocol k8scorev1.Protocol + + k8sService.TypeMeta.APIVersion = "v1" + k8sService.TypeMeta.Kind = "Service" + if service.Name != nil { + k8sService.ObjectMeta.Name = *service.Name + } else { + log.Println("Service without a name is not recommended") + } + k8sService.ObjectMeta.Annotations = make(map[string]string) + + // default TCP unless service.Protocol is equal to k8scorev1.ProtocolUDP + if service.Protocol != nil && k8scorev1.Protocol(strings.ToUpper(*service.Protocol)) == k8scorev1.ProtocolUDP { + protocol = k8scorev1.ProtocolUDP + } else { + protocol = k8scorev1.ProtocolTCP + } + + if service.Port != nil { + sPort := k8scorev1.ServicePort{ + Protocol: protocol, + Port: int32(*service.Port), + TargetPort: intstr.IntOrString{IntVal: int32(*service.Port)}, + } + k8sService.Spec.Ports = append(k8sService.Spec.Ports, sPort) + } + + if service.Name != nil { + k8sService.Spec.Selector = map[string]string{"app": *service.Name} + } else { + log.Println("Service without a name is not recommended") + } + + populateKICServiceProxyAndUpstreamCustomResources(content, &service, &k8sService, kicContent) + + // iterate over the plugins for this service, create a KongPlugin for each one and add an annotation to the service + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + // create a plugins annotation in the k8sservice to link the plugin to it + error := addPluginsToService(service, k8sService, kicContent) + if error != nil { + return error + } + kicContent.Services = append(kicContent.Services, k8sService) + + } + return nil +} + +func populateKICIngressesWithCustomResources(content *Content, kicContent *KICContent) error { + + // Transform routes into k8s Ingress and KongIngress resources + // Assume each pair host/path will get its own ingress manifest + for _, service := range content.Services { + for _, route := range service.Routes { + // save all ingresses we create for this route so we can then + // assign them the plugins defined for the route + var routeIngresses []k8snetv1.Ingress + + // if there are no hosts just use the paths + if len(route.Hosts) == 0 { + routeIngresses = KongRoutePathToIngressPathCustomResources(route, nil, routeIngresses, kicContent, service) + } else { + // iterate over the hosts and paths and create an ingress for each + + for _, host := range route.Hosts { + // create a KongIngress resource and copy route data into it + // add annotation to the ingress to link it to the kongIngress + routeIngresses = KongRoutePathToIngressPathCustomResources(route, host, routeIngresses, kicContent, service) + + } + } + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + // create a plugins annotation in the routeIngresses to link them to this plugin. + // separate plugins with commas + error := addPluginsToRoute(route, routeIngresses, kicContent) + if error != nil { + return error + } + } + } + return nil + +} + +func KongRoutePathToIngressPathCustomResources(route *FRoute, host *string, routeIngresses []k8snetv1.Ingress, kicContent *KICContent, service FService) []k8snetv1.Ingress { + for _, path := range route.Paths { + var k8sIngress k8snetv1.Ingress + var pathTypeImplSpecific k8snetv1.PathType = k8snetv1.PathTypeImplementationSpecific + k8sIngress.TypeMeta.APIVersion = "networking.k8s.io/v1" + k8sIngress.TypeMeta.Kind = "Ingress" + k8sIngress.ObjectMeta.Name = *route.Name + ingressClassName := "kong" + k8sIngress.Spec.IngressClassName = &ingressClassName + + // Host and/or Service.Port can be nil. There are 4 possible combinations. + // host == nil && service.Port == nil + fillIngressHostAndPortSection(host, service, &k8sIngress, path, pathTypeImplSpecific) + + // Create a KongIngress resource and copy Kong specific route data into it + var kongIngress kicv1.KongIngress + kongIngress.APIVersion = "configuration.konghq.com/v1" + kongIngress.Kind = "KongIngress" + kongIngress.ObjectMeta.Name = *route.Name + kongIngress.ObjectMeta.Annotations = map[string]string{"kubernetes.io/ingress.class": "kong"} + + var kongProtocols []*kicv1.KongProtocol + for _, protocol := range route.Protocols { + p := kicv1.KongProtocol(*protocol) + kongProtocols = append(kongProtocols, &p) + } + + kongIngress.Route = &kicv1.KongIngressRoute{ + Methods: route.Methods, + Protocols: kongProtocols, + StripPath: route.StripPath, + PreserveHost: route.PreserveHost, + RegexPriority: route.RegexPriority, + HTTPSRedirectStatusCode: route.HTTPSRedirectStatusCode, + Headers: route.Headers, + PathHandling: route.PathHandling, + SNIs: route.SNIs, + RequestBuffering: route.RequestBuffering, + ResponseBuffering: route.ResponseBuffering, + } + + // add an annotation to the k8sIngress to link it to the kongIngress + k8sIngress.ObjectMeta.Annotations = map[string]string{"konghq.com/override": kongIngress.ObjectMeta.Name} + + routeIngresses = append(routeIngresses, k8sIngress) + + kicContent.Ingresses = append(kicContent.Ingresses, k8sIngress) + kicContent.KongIngresses = append(kicContent.KongIngresses, kongIngress) + } + return routeIngresses +} + +///// +// Functions for ANNOTATION based manifests +///// + +func populateKICServicesWithAnnotations(content *Content, kicContent *KICContent) error { + + // Iterate Kong Services and create k8s Services, + // then create KongIngress resources for Kong Service Upstream data. + // Finally, create KongPlugin resources for each plugin + // associated with the service. + for _, service := range content.Services { + var k8sService k8scorev1.Service + var protocol k8scorev1.Protocol + + k8sService.TypeMeta.APIVersion = "v1" + k8sService.TypeMeta.Kind = "Service" + if service.Name != nil { + k8sService.ObjectMeta.Name = *service.Name + } else { + log.Println("Service without a name is not recommended") + } + k8sService.ObjectMeta.Annotations = make(map[string]string) + + // default TCP unless service.Protocol is equal to k8scorev1.ProtocolUDP + if service.Protocol != nil && k8scorev1.Protocol(strings.ToUpper(*service.Protocol)) == k8scorev1.ProtocolUDP { + protocol = k8scorev1.ProtocolUDP + } else { + protocol = k8scorev1.ProtocolTCP + } + + if service.Port != nil { + sPort := k8scorev1.ServicePort{ + Protocol: protocol, + Port: int32(*service.Port), + TargetPort: intstr.IntOrString{IntVal: int32(*service.Port)}, + } + k8sService.Spec.Ports = append(k8sService.Spec.Ports, sPort) + } + + if service.Name != nil { + k8sService.Spec.Selector = map[string]string{"app": *service.Name} + } else { + log.Println("Service without a name is not recommended") + } + + // add konghq.com/read-timeout annotation if service.ReadTimeout is not nil + if service.ReadTimeout != nil { + k8sService.ObjectMeta.Annotations["konghq.com/read-timeout"] = strconv.Itoa(*service.ReadTimeout) + } + + // add konghq.com/write-timeout annotation if service.WriteTimeout is not nil + if service.WriteTimeout != nil { + k8sService.ObjectMeta.Annotations["konghq.com/write-timeout"] = strconv.Itoa(*service.WriteTimeout) + } + + // add konghq.com/connect-timeout annotation if service.ConnectTimeout is not nil + if service.ConnectTimeout != nil { + k8sService.ObjectMeta.Annotations["konghq.com/connect-timeout"] = strconv.Itoa(*service.ConnectTimeout) + } + + // add konghq.com/protocol annotation if service.Protocol is not nil + if service.Protocol != nil { + k8sService.ObjectMeta.Annotations["konghq.com/protocol"] = *service.Protocol + } + + // add konghq.com/path annotation if service.Path is not nil + if service.Path != nil { + k8sService.ObjectMeta.Annotations["konghq.com/path"] = *service.Path + } + + // add konghq.com/retries annotation if service.Retries is not nil + if service.Retries != nil { + k8sService.ObjectMeta.Annotations["konghq.com/retries"] = strconv.Itoa(*service.Retries) + } + + populateKICUpstream(content, &service, &k8sService, kicContent) + + // iterate over the plugins for this service, create a KongPlugin for each one and add an annotation to the service + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + // create a plugins annotation in the k8sservice to link the plugin to it + error := addPluginsToService(service, k8sService, kicContent) + if error != nil { + return error + } + + kicContent.Services = append(kicContent.Services, k8sService) + + } + return nil +} + +func populateKICIngressesWithAnnotations(content *Content, kicContent *KICContent) error { + + // Transform routes into k8s Ingress and KongIngress resources + // Assume each pair host/path will get its own ingress manifest + for _, service := range content.Services { + for _, route := range service.Routes { + // save all ingresses we create for this route so we can then + // assign them the plugins defined for the route + var routeIngresses []k8snetv1.Ingress + + // if there are no hosts just use the paths + if len(route.Hosts) == 0 { + routeIngresses = KongRoutePathToIngressPathAnnotations(route, nil, routeIngresses, kicContent, service) + } else { + // iterate over the hosts and paths and create an ingress for each + + for _, host := range route.Hosts { + // create a KongIngress resource and copy route data into it + // add annotation to the ingress to link it to the kongIngress + routeIngresses = KongRoutePathToIngressPathAnnotations(route, host, routeIngresses, kicContent, service) + + } + } + // transform the plugin config from map[string]interface{} to apiextensionsv1.JSON + // create a plugins annotation in the routeIngresses to link them to this plugin. + // separate plugins with commas + error := addPluginsToRoute(route, routeIngresses, kicContent) + if error != nil { + return error + } + } + } + return nil +} + +func KongRoutePathToIngressPathAnnotations(route *FRoute, host *string, routeIngresses []k8snetv1.Ingress, file *KICContent, service FService) []k8snetv1.Ingress { + for _, path := range route.Paths { + var k8sIngress k8snetv1.Ingress + var pathTypeImplSpecific k8snetv1.PathType = k8snetv1.PathTypeImplementationSpecific + k8sIngress.TypeMeta.APIVersion = "networking.k8s.io/v1" + k8sIngress.TypeMeta.Kind = "Ingress" + k8sIngress.ObjectMeta.Name = *route.Name + ingressClassName := "kong" + k8sIngress.Spec.IngressClassName = &ingressClassName + k8sIngress.ObjectMeta.Annotations = make(map[string]string) + + // Host and/or Service.Port can be nil. There are 4 possible combinations. + // host == nil && service.Port == nil + fillIngressHostAndPortSection(host, service, &k8sIngress, path, pathTypeImplSpecific) + + // add konghq.com/protocols annotation if route.Protocols is not nil + if route.Protocols != nil { + var protocols string + for _, protocol := range route.Protocols { + if protocols == "" { + protocols = *protocol + } else { + protocols = protocols + "," + *protocol + } + } + k8sIngress.ObjectMeta.Annotations["konghq.com/protocols"] = protocols + } + + // add konghq.com/strip-path annotation if route.StripPath is not nil + if route.StripPath != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/strip-path"] = strconv.FormatBool(*route.StripPath) + } + + // add konghq.com/preserve-host annotation if route.PreserveHost is not nil + if route.PreserveHost != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/preserve-host"] = strconv.FormatBool(*route.PreserveHost) + } + + // add konghq.com/regex-priority annotation if route.RegexPriority is not nil + if route.RegexPriority != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/regex-priority"] = strconv.Itoa(*route.RegexPriority) + } + + // add konghq.com/https-redirect-status-code annotation if route.HTTPSRedirectStatusCode is not nil + if route.HTTPSRedirectStatusCode != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/https-redirect-status-code"] = strconv.Itoa(*route.HTTPSRedirectStatusCode) + } + + // add konghq.com/headers.* annotation if route.Headers is not nil + if route.Headers != nil { + for key, value := range route.Headers { + k8sIngress.ObjectMeta.Annotations["konghq.com/headers."+key] = strings.Join(value, ",") + } + } + + // add konghq.com/path-handling annotation if route.PathHandling is not nil + if route.PathHandling != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/path-handling"] = *route.PathHandling + } + + // add konghq.com/snis annotation if route.SNIs is not nil + if route.SNIs != nil { + var snis string + for _, sni := range route.SNIs { + if snis == "" { + snis = *sni + } else { + snis = snis + "," + *sni + } + } + k8sIngress.ObjectMeta.Annotations["konghq.com/snis"] = snis + } + + // add konghq.com/request-buffering annotation if route.RequestBuffering is not nil + if route.RequestBuffering != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/request-buffering"] = strconv.FormatBool(*route.RequestBuffering) + } + + // add konghq.com/response-buffering annotation if route.ResponseBuffering is not nil + if route.ResponseBuffering != nil { + k8sIngress.ObjectMeta.Annotations["konghq.com/response-buffering"] = strconv.FormatBool(*route.ResponseBuffering) + } + + // add konghq.com/methods annotation if route.Methods is not nil + if route.Methods != nil { + var methods string + for _, method := range route.Methods { + if methods == "" { + methods = *method + } else { + methods = methods + "," + *method + } + } + k8sIngress.ObjectMeta.Annotations["konghq.com/methods"] = methods + } + + routeIngresses = append(routeIngresses, k8sIngress) + + file.Ingresses = append(file.Ingresses, k8sIngress) + } + return routeIngresses +} diff --git a/file/kong2kic_test.go b/file/kong2kic_test.go new file mode 100644 index 000000000..4acf34ec4 --- /dev/null +++ b/file/kong2kic_test.go @@ -0,0 +1,209 @@ +package file + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_convertKongGatewayToIngress(t *testing.T) { + type args struct { + inputFilename string + outputFilenameYamlCRD string + outputFilenameYamlAnnotation string + outputFilenameJsonCRD string + outputFilenameJsonAnnotation string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "convert one service", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/1-service/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/1-service/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/1-service/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/1-service/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/1-service/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service and one route", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/2-service-and-route/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/2-service-and-route/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/2-service-and-route/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/2-service-and-route/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/2-service-and-route/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream data", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/3-service-and-upstream/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/3-service-and-upstream/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/3-service-and-upstream/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream and route", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/4-service-route-upstream/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/4-service-route-upstream/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/4-service-route-upstream/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, acl auth plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, basic auth plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, jwt auth plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, key auth plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, mtls auth plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert one service with upstream, route, multiple plugin", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/output-expected.json", + }, + wantErr: false, + }, + { + name: "convert consumer groups", + args: args{ + inputFilename: "testdata/kong2kic/custom_resources/yaml/11-consumer-group/input.yaml", + outputFilenameYamlCRD: "testdata/kong2kic/custom_resources/yaml/11-consumer-group/output-expected.yaml", + outputFilenameJsonCRD: "testdata/kong2kic/custom_resources/json/11-consumer-group/output-expected.json", + outputFilenameYamlAnnotation: "testdata/kong2kic/annotations/yaml/11-consumer-group/output-expected.yaml", + outputFilenameJsonAnnotation: "testdata/kong2kic/annotations/json/11-consumer-group/output-expected.json", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inputContent, err := GetContentFromFiles([]string{tt.args.inputFilename}, false) + if err != nil { + assert.Fail(t, err.Error()) + } + + output, err := MarshalKongToKICYaml(inputContent, CUSTOM_RESOURCE) + if (err != nil) != tt.wantErr { + t.Errorf("KongToKIC() error = %v, wantErr %v", err, tt.wantErr) + } + + if err == nil { + + expected, err := os.ReadFile(tt.args.outputFilenameYamlCRD) + if err != nil { + assert.Fail(t, err.Error()) + } + assert.Equal(t, string(expected), string(output)) + } + + output, err = MarshalKongToKICYaml(inputContent, ANNOTATIONS) + if (err != nil) != tt.wantErr { + t.Errorf("KongToKIC() error = %v, wantErr %v", err, tt.wantErr) + } + + if err == nil { + + expected, err := os.ReadFile(tt.args.outputFilenameYamlAnnotation) + if err != nil { + assert.Fail(t, err.Error()) + } + assert.Equal(t, string(expected), string(output)) + } + + output, err = MarshalKongToKICJson(inputContent, CUSTOM_RESOURCE) + if (err != nil) != tt.wantErr { + t.Errorf("KongToKIC() error = %v, wantErr %v", err, tt.wantErr) + } + + if err == nil { + + expected, err := os.ReadFile(tt.args.outputFilenameJsonCRD) + if err != nil { + assert.Fail(t, err.Error()) + } + assert.Equal(t, string(expected), string(output)) + } + + output, err = MarshalKongToKICJson(inputContent, ANNOTATIONS) + if (err != nil) != tt.wantErr { + t.Errorf("KongToKIC() error = %v, wantErr %v", err, tt.wantErr) + } + + if err == nil { + + expected, err := os.ReadFile(tt.args.outputFilenameJsonAnnotation) + if err != nil { + assert.Fail(t, err.Error()) + } + assert.Equal(t, string(expected), string(output)) + } + }) + } +} diff --git a/file/kong_json_schema.json b/file/kong_json_schema.json index b287a13ff..76d09f68a 100644 --- a/file/kong_json_schema.json +++ b/file/kong_json_schema.json @@ -444,7 +444,6 @@ }, "groups": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/ConsumerGroup" }, "type": "array" @@ -509,12 +508,14 @@ { "required": [ "username" - ] + ], + "description": "at least one of custom_id or username must be set" }, { "required": [ - "id" - ] + "custom_id" + ], + "description": "at least one of custom_id or username must be set" } ] }, @@ -600,6 +601,9 @@ "consumer": { "type": "string" }, + "consumer_group": { + "type": "string" + }, "created_at": { "type": "integer" }, diff --git a/file/reader.go b/file/reader.go index 81ba985e7..1e53895ff 100644 --- a/file/reader.go +++ b/file/reader.go @@ -33,12 +33,12 @@ type RenderConfig struct { // // It will return an error if the file representation is invalid // or if there is any error during processing. -func GetContentFromFiles(filenames []string) (*Content, error) { +func GetContentFromFiles(filenames []string, mockEnvVars bool) (*Content, error) { if len(filenames) == 0 { return nil, ErrorFilenameEmpty } - return getContent(filenames) + return getContent(filenames, mockEnvVars) } // GetForKonnect processes the fileContent and renders a RawState and KonnectRawState diff --git a/file/reader_test.go b/file/reader_test.go index ebf5e7ea0..aae486134 100644 --- a/file/reader_test.go +++ b/file/reader_test.go @@ -68,7 +68,7 @@ func TestReadKongStateFromStdinFailsToParseText(t *testing.T) { os.Stdin = tmpfile - c, err := GetContentFromFiles(filenames) + c, err := GetContentFromFiles(filenames, false) assert.NotNil(err) assert.Nil(c) } @@ -97,7 +97,7 @@ func TestTransformNotFalse(t *testing.T) { os.Stdin = tmpfile - c, err := GetContentFromFiles(filenames) + c, err := GetContentFromFiles(filenames, false) if err != nil { panic(err) } @@ -139,7 +139,7 @@ func TestReadKongStateFromStdin(t *testing.T) { os.Stdin = tmpfile - c, err := GetContentFromFiles(filenames) + c, err := GetContentFromFiles(filenames, false) assert.NotNil(c) assert.Nil(err) @@ -155,7 +155,7 @@ func TestReadKongStateFromFile(t *testing.T) { assert := assert.New(t) assert.Equal("testdata/config.yaml", filenames[0]) - c, err := GetContentFromFiles(filenames) + c, err := GetContentFromFiles(filenames, false) assert.NotNil(c) assert.Nil(err) diff --git a/file/readfile.go b/file/readfile.go index 93054c8e3..165f7b6b1 100644 --- a/file/readfile.go +++ b/file/readfile.go @@ -19,45 +19,55 @@ import ( // getContent reads all the YAML and JSON files in the directory or the // file, depending on the type of each item in filenames, merges the content of // these files and renders a Content. -func getContent(filenames []string) (*Content, error) { - var allReaders []io.Reader - var workspaces []string +func getContent(filenames []string, mockEnvVars bool) (*Content, error) { + var workspaces, runtimeGroups []string + var res Content + var errs []error for _, fileOrDir := range filenames { readers, err := getReaders(fileOrDir) if err != nil { return nil, err } - allReaders = append(allReaders, readers...) - } - var res Content - for _, r := range allReaders { - content, err := readContent(r) - if err != nil { - return nil, fmt.Errorf("reading file: %w", err) - } - if content.Workspace != "" { - workspaces = append(workspaces, content.Workspace) - } - err = mergo.Merge(&res, content, mergo.WithAppendSlice) - if err != nil { - return nil, fmt.Errorf("merging file contents: %w", err) + + for filename, r := range readers { + content, err := readContent(r, mockEnvVars) + if err != nil { + errs = append(errs, fmt.Errorf("reading file %s: %w", filename, err)) + continue + } + if content.Workspace != "" { + workspaces = append(workspaces, content.Workspace) + } + if content.Konnect != nil && len(content.Konnect.RuntimeGroupName) > 0 { + runtimeGroups = append(runtimeGroups, content.Konnect.RuntimeGroupName) + } + err = mergo.Merge(&res, content, mergo.WithAppendSlice) + if err != nil { + return nil, fmt.Errorf("merging file contents: %w", err) + } } } + if len(errs) > 0 { + return nil, utils.ErrArray{Errors: errs} + } if err := validateWorkspaces(workspaces); err != nil { return nil, err } + if err := validateRuntimeGroups(runtimeGroups); err != nil { + return nil, err + } return &res, nil } -// getReaders returns back io.Readers representing all the YAML and JSON -// files in a directory. If fileOrDir is a single file, then it +// getReaders returns back a map of filename:io.Reader representing all the +// YAML and JSON files in a directory. If fileOrDir is a single file, then it // returns back the reader for the file. // If fileOrDir is equal to "-" string, then it returns back a io.Reader // for the os.Stdin file descriptor. -func getReaders(fileOrDir string) ([]io.Reader, error) { +func getReaders(fileOrDir string) (map[string]io.Reader, error) { // special case where `-` means stdin if fileOrDir == "-" { - return []io.Reader{os.Stdin}, nil + return map[string]io.Reader{"STDIN": os.Stdin}, nil } finfo, err := os.Stat(fileOrDir) @@ -75,13 +85,13 @@ func getReaders(fileOrDir string) ([]io.Reader, error) { files = append(files, fileOrDir) } - var res []io.Reader + res := make(map[string]io.Reader, len(files)) for _, file := range files { f, err := os.Open(file) if err != nil { return nil, fmt.Errorf("opening file: %w", err) } - res = append(res, bufio.NewReader(f)) + res[file] = bufio.NewReader(f) } return res, nil } @@ -95,13 +105,13 @@ func hasLeadingSpace(fileContent string) bool { // readContent reads all the byes until io.EOF and unmarshals the read // bytes into Content. -func readContent(reader io.Reader) (*Content, error) { +func readContent(reader io.Reader, mockEnvVars bool) (*Content, error) { var err error contentBytes, err := ioutil.ReadAll(reader) if err != nil { return nil, err } - renderedContent, err := renderTemplate(string(contentBytes)) + renderedContent, err := renderTemplate(string(contentBytes), mockEnvVars) if err != nil { return nil, fmt.Errorf("parsing file: %w", err) } @@ -146,31 +156,73 @@ func getPrefixedEnvVar(key string) (string, error) { return value, nil } +// getPrefixedEnvVarMocked is used when we mock the env variables while rendering a template. +// It will always return the name of the environment variable in this case. +func getPrefixedEnvVarMocked(key string) (string, error) { + const envVarPrefix = "DECK_" + if !strings.HasPrefix(key, envVarPrefix) { + return "", fmt.Errorf("environment variables in the state file must "+ + "be prefixed with 'DECK_', found: '%s'", key) + } + return key, nil +} + func toBool(key string) (bool, error) { return strconv.ParseBool(key) } +// toBoolMocked is used when we mock the env variables while rendering a template. +// It will always return false in this case. +func toBoolMocked(_ string) (bool, error) { + return false, nil +} + func toInt(key string) (int, error) { return strconv.Atoi(key) } +// toIntMocked is used when we mock the env variables while rendering a template. +// It will always return 42 in this case. +func toIntMocked(_ string) (int, error) { + return 42, nil +} + func toFloat(key string) (float64, error) { return strconv.ParseFloat(key, 64) } +// toFloatMocked is used when we mock the env variables while rendering a template. +// It will always return 42 in this case. +func toFloatMocked(_ string) (float64, error) { + return 42, nil +} + func indent(spaces int, v string) string { pad := strings.Repeat(" ", spaces) return strings.Replace(v, "\n", "\n"+pad, -1) } -func renderTemplate(content string) (string, error) { - t := template.New("state").Funcs(template.FuncMap{ - "env": getPrefixedEnvVar, - "toBool": toBool, - "toInt": toInt, - "toFloat": toFloat, - "indent": indent, - }).Delims("${{", "}}") +func renderTemplate(content string, mockEnvVars bool) (string, error) { + var templateFuncs template.FuncMap + if mockEnvVars { + templateFuncs = template.FuncMap{ + "env": getPrefixedEnvVarMocked, + "toBool": toBoolMocked, + "toInt": toIntMocked, + "toFloat": toFloatMocked, + "indent": indent, + } + } else { + templateFuncs = template.FuncMap{ + "env": getPrefixedEnvVar, + "toBool": toBool, + "toInt": toInt, + "toFloat": toFloat, + "indent": indent, + } + } + t := template.New("state").Funcs(templateFuncs).Delims("${{", "}}") + t, err := t.Parse(content) if err != nil { return "", err diff --git a/file/readfile_test.go b/file/readfile_test.go index df2a49285..573f0aa45 100644 --- a/file/readfile_test.go +++ b/file/readfile_test.go @@ -6,9 +6,10 @@ import ( "reflect" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" - "github.com/stretchr/testify/require" ) func Test_configFilesInDir(t *testing.T) { @@ -66,15 +67,17 @@ func Test_getReaders(t *testing.T) { tests := []struct { name string args args - want []io.Reader + want map[string]io.Reader // length of returned array wantLen int wantErr bool }{ { - name: "read from standard input", - args: args{"-"}, - want: []io.Reader{os.Stdin}, + name: "read from standard input", + args: args{"-"}, + want: map[string]io.Reader{ + "STDIN": os.Stdin, + }, wantLen: 1, wantErr: false, }, @@ -126,6 +129,29 @@ func Test_getReaders(t *testing.T) { } } +func sortSlices(x, y interface{}) bool { + var xName, yName string + switch xEntity := x.(type) { + case FService: + yEntity := y.(FService) + xName = *xEntity.Name + yName = *yEntity.Name + case FRoute: + yEntity := y.(FRoute) + xName = *xEntity.Name + yName = *yEntity.Name + case FConsumer: + yEntity := y.(FConsumer) + xName = *xEntity.Username + yName = *yEntity.Username + case FPlugin: + yEntity := y.(FPlugin) + xName = *xEntity.Name + yName = *yEntity.Name + } + return xName < yName +} + func Test_getContent(t *testing.T) { type args struct { filenames []string @@ -372,6 +398,12 @@ kong.log.set_serialize_value("span_id", parse_traceid(ngx.ctx.KONG_SPANS[1].span want: nil, wantErr: true, }, + { + name: "different runtime groups", + args: args{[]string{"testdata/differentruntimegroup"}}, + want: nil, + wantErr: true, + }, { name: "same workspaces", args: args{[]string{"testdata/sameworkspace"}}, @@ -551,12 +583,20 @@ kong.log.set_serialize_value("span_id", parse_traceid(ngx.ctx.KONG_SPANS[1].span for k, v := range tt.envVars { t.Setenv(k, v) } - got, err := getContent(tt.args.filenames) + got, err := getContent(tt.args.filenames, false) if (err != nil) != tt.wantErr { t.Errorf("getContent() error = %v, wantErr %v", err, tt.wantErr) return } - require.Equal(t, tt.want, got) + + opt := []cmp.Option{ + cmpopts.SortSlices(sortSlices), + cmpopts.SortSlices(func(a, b *string) bool { return *a < *b }), + cmpopts.EquateEmpty(), + } + if diff := cmp.Diff(got, tt.want, opt...); diff != "" { + t.Errorf(diff) + } }) } } diff --git a/file/testdata/differentruntimegroup/bar.yaml b/file/testdata/differentruntimegroup/bar.yaml new file mode 100644 index 000000000..a613369e2 --- /dev/null +++ b/file/testdata/differentruntimegroup/bar.yaml @@ -0,0 +1,6 @@ +_format_version: "3.0" +_konnect: + runtime_group_name: bar +services: +- name: svc2 + host: 2.example.com \ No newline at end of file diff --git a/file/testdata/differentruntimegroup/foo.yaml b/file/testdata/differentruntimegroup/foo.yaml new file mode 100644 index 000000000..8a91952b2 --- /dev/null +++ b/file/testdata/differentruntimegroup/foo.yaml @@ -0,0 +1,6 @@ +_format_version: "3.0" +_konnect: + runtime_group_name: foo +services: +- name: svc1 + host: 1.example.com \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/1-service/input.yaml b/file/testdata/kong2kic/annotations/json/1-service/input.yaml new file mode 100644 index 000000000..2632c2c53 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/1-service/input.yaml @@ -0,0 +1,16 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/1-service/output-expected.json b/file/testdata/kong2kic/annotations/json/1-service/output-expected.json new file mode 100644 index 000000000..69c6f5b8e --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/1-service/output-expected.json @@ -0,0 +1,31 @@ +{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/input.yaml b/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/input.yaml new file mode 100644 index 000000000..316ff1b35 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/input.yaml @@ -0,0 +1,194 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: aws-lambda + config: + aws_key: my_key + aws_secret: my_secret + function_name: my_function + aws_region: us-west-2 + - name: cors + config: + origins: + - example.com + methods: + - GET + - POST + headers: + - Authorization + exposed_headers: + - X-My-Header + max_age: 3600 + credentials: true + - name: file-log + config: + path: /var/log/kong/kong.log + reopen: true + - name: http-log + config: + http_endpoint: http://example.com/logs + method: POST + content_type: application/json + timeout: 10000 + keepalive: 60000 + retry_count: 10 + queue_size: 1000 + - name: ip-restriction + config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 + - name: rate-limiting-advanced + config: + limit: + - 5 + window_size: + - 30 + identifier: consumer + sync_rate: -1 + namespace: example_namespace + strategy: local + hide_client_headers: false + - name: request-termination + config: + status_code: 403 + message: Forbidden + - name: response-ratelimiting + config: + limits: + limit_name: + minute: 10 + policy: local + - name: tcp-log + config: + host: example.com + port: 1234 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/output-expected.json b/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/output-expected.json new file mode 100644 index 000000000..8bf257ff6 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/10-mulitple-plugins-same-route/output-expected.json @@ -0,0 +1,371 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "aws-lambda", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "aws_key": "my_key", + "aws_region": "us-west-2", + "aws_secret": "my_secret", + "function_name": "my_function" + }, + "plugin": "aws-lambda", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "cors", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "credentials": true, + "exposed_headers": [ + "X-My-Header" + ], + "headers": [ + "Authorization" + ], + "max_age": 3600, + "methods": [ + "GET", + "POST" + ], + "origins": [ + "example.com" + ] + }, + "plugin": "cors", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "file-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "path": "/var/log/kong/kong.log", + "reopen": true + }, + "plugin": "file-log", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "http-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "content_type": "application/json", + "http_endpoint": "http://example.com/logs", + "keepalive": 60000, + "method": "POST", + "queue_size": 1000, + "retry_count": 10, + "timeout": 10000 + }, + "plugin": "http-log", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "ip-restriction", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "allow": [ + "192.168.0.1/24" + ], + "deny": [ + "192.168.0.2/32" + ] + }, + "plugin": "ip-restriction", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "rate-limiting-advanced", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_client_headers": false, + "identifier": "consumer", + "limit": [ + 5 + ], + "namespace": "example_namespace", + "strategy": "local", + "sync_rate": -1, + "window_size": [ + 30 + ] + }, + "plugin": "rate-limiting-advanced", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "request-termination", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "message": "Forbidden", + "status_code": 403 + }, + "plugin": "request-termination", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "response-ratelimiting", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "limits": { + "limit_name": { + "minute": 10 + } + }, + "policy": "local" + }, + "plugin": "response-ratelimiting", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "tcp-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "host": "example.com", + "port": 1234 + }, + "plugin": "tcp-log", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "aws-lambda,cors,file-log,http-log,ip-restriction,rate-limiting-advanced,request-termination,response-ratelimiting,tcp-log", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/11-consumer-group/input.yaml b/file/testdata/kong2kic/annotations/json/11-consumer-group/input.yaml new file mode 100644 index 000000000..b57971e33 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/11-consumer-group/input.yaml @@ -0,0 +1,9 @@ +consumer_groups: + - name: example-consumer-group + consumers: + - username: example-user +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/11-consumer-group/output-expected.json b/file/testdata/kong2kic/annotations/json/11-consumer-group/output-expected.json new file mode 100644 index 000000000..3e8cf4b1e --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/11-consumer-group/output-expected.json @@ -0,0 +1,28 @@ +{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "consumerGroups": [ + "example-consumer-group" + ], + "status": {} +}{ + "kind": "KongConsumerGroup", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-consumer-group", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/2-service-and-route/input.yaml b/file/testdata/kong2kic/annotations/json/2-service-and-route/input.yaml new file mode 100644 index 000000000..96ffcce6e --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/2-service-and-route/input.yaml @@ -0,0 +1,44 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/2-service-and-route/output-expected.json b/file/testdata/kong2kic/annotations/json/2-service-and-route/output-expected.json new file mode 100644 index 000000000..62667827e --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/2-service-and-route/output-expected.json @@ -0,0 +1,76 @@ +{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/3-service-and-upstream/input.yaml b/file/testdata/kong2kic/annotations/json/3-service-and-upstream/input.yaml new file mode 100644 index 000000000..0ee8eeadd --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/3-service-and-upstream/input.yaml @@ -0,0 +1,97 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/annotations/json/3-service-and-upstream/output-expected.json b/file/testdata/kong2kic/annotations/json/3-service-and-upstream/output-expected.json new file mode 100644 index 000000000..f770a97cf --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/3-service-and-upstream/output-expected.json @@ -0,0 +1,131 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/4-service-route-upstream/input.yaml b/file/testdata/kong2kic/annotations/json/4-service-route-upstream/input.yaml new file mode 100644 index 000000000..9e4e5ec86 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/4-service-route-upstream/input.yaml @@ -0,0 +1,125 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/annotations/json/4-service-route-upstream/output-expected.json b/file/testdata/kong2kic/annotations/json/4-service-route-upstream/output-expected.json new file mode 100644 index 000000000..10f4d1792 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/4-service-route-upstream/output-expected.json @@ -0,0 +1,176 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/input.yaml b/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/input.yaml new file mode 100644 index 000000000..e9fb52034 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: acl + config: + allow: + - admin +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + acls: + - group: acl_group + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/output-expected.json b/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/output-expected.json new file mode 100644 index 000000000..3cb78115e --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/5-service-route-upstream-acl-auth/output-expected.json @@ -0,0 +1,224 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "acl", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "allow": [ + "admin" + ] + }, + "plugin": "acl", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "acl", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "acl-group-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "group": "acl_group", + "kongCredType": "acl" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "acl-group-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/input.yaml b/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/input.yaml new file mode 100644 index 000000000..163846d4b --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: basic-auth + config: + hide_credentials: false +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + basicauth_credentials: + - username: my_basic_user + password: my_basic_password + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/output-expected.json b/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/output-expected.json new file mode 100644 index 000000000..9842a161a --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/6-service-route-upstream-basic-auth/output-expected.json @@ -0,0 +1,223 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "basic-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_credentials": false + }, + "plugin": "basic-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "basic-auth", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "basic-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "kongCredType": "basic-auth", + "password": "my_basic_password", + "username": "my_basic_user" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "basic-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/input.yaml b/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/input.yaml new file mode 100644 index 000000000..363f14ea3 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/input.yaml @@ -0,0 +1,151 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: jwt + config: + uri_param_names: + - token + claims_to_verify: + - exp + - nbf + key_claim_name: kid + secret_is_base64: false + anonymous: null + run_on_preflight: true + maximum_expiration: 3600 + header_names: + - Authorization +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + jwt_secrets: + - key: my_jwt_secret + algorithm: HS256 + secret: my_secret_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/output-expected.json b/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/output-expected.json new file mode 100644 index 000000000..109116be9 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/7-service-route-upstream-jwt-auth/output-expected.json @@ -0,0 +1,238 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "jwt", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "anonymous": null, + "claims_to_verify": [ + "exp", + "nbf" + ], + "header_names": [ + "Authorization" + ], + "key_claim_name": "kid", + "maximum_expiration": 3600, + "run_on_preflight": true, + "secret_is_base64": false, + "uri_param_names": [ + "token" + ] + }, + "plugin": "jwt", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "jwt", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "jwt-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "algorithm": "HS256", + "key": "my_jwt_secret", + "kongCredType": "jwt", + "secret": "my_secret_key" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "jwt-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/input.yaml b/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/input.yaml new file mode 100644 index 000000000..754bb520c --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/input.yaml @@ -0,0 +1,142 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: key-auth + config: + hide_credentials: false + key_names: + - apikey + key_in_body: false + run_on_preflight: true +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + keyauth_credentials: + - key: my_api_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/output-expected.json b/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/output-expected.json new file mode 100644 index 000000000..98a725690 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/8-service-route-upstream-key-auth/output-expected.json @@ -0,0 +1,227 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "key-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_credentials": false, + "key_in_body": false, + "key_names": [ + "apikey" + ], + "run_on_preflight": true + }, + "plugin": "key-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "key-auth", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "key-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "key": "my_api_key", + "kongCredType": "key-auth" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "key-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/input.yaml b/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/input.yaml new file mode 100644 index 000000000..f12055648 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/input.yaml @@ -0,0 +1,140 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: mtls-auth + config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + skip_consumer_lookup: false + revocation_check_mode: SKIP +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + mtls_auth_credentials: + - id: cce8c384-721f-4f58-85dd-50834e3e733a + subject_name: example-user@example.com \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/output-expected.json b/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/output-expected.json new file mode 100644 index 000000000..de585a5b6 --- /dev/null +++ b/file/testdata/kong2kic/annotations/json/9-service-route-upstream-mtls-auth/output-expected.json @@ -0,0 +1,228 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "mtls-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "ca_certificates": [ + "cce8c384-721f-4f58-85dd-50834e3e733a" + ], + "revocation_check_mode": "SKIP", + "skip_consumer_lookup": false + }, + "plugin": "mtls-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/headers.x-another-header": "bla", + "konghq.com/headers.x-my-header": "foo,bar", + "konghq.com/https-redirect-status-code": "302", + "konghq.com/methods": "GET,POST", + "konghq.com/plugins": "mtls-auth", + "konghq.com/preserve-host": "true", + "konghq.com/protocols": "http,https", + "konghq.com/regex-priority": "1", + "konghq.com/snis": "example.com", + "konghq.com/strip-path": "false" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/connect-timeout": "5000", + "konghq.com/override": "example-service", + "konghq.com/path": "/v1", + "konghq.com/protocol": "http", + "konghq.com/read-timeout": "60000", + "konghq.com/retries": "5", + "konghq.com/write-timeout": "60000" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "mtls-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "id": "cce8c384-721f-4f58-85dd-50834e3e733a", + "kongCredType": "mtls-auth", + "subject_name": "example-user@example.com" + }, + "type": "Opaque" +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "mtls-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/1-service/input.yaml b/file/testdata/kong2kic/annotations/yaml/1-service/input.yaml new file mode 100644 index 000000000..2632c2c53 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/1-service/input.yaml @@ -0,0 +1,16 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/1-service/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/1-service/output-expected.yaml new file mode 100644 index 000000000..9df248f36 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/1-service/output-expected.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/input.yaml b/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/input.yaml new file mode 100644 index 000000000..316ff1b35 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/input.yaml @@ -0,0 +1,194 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: aws-lambda + config: + aws_key: my_key + aws_secret: my_secret + function_name: my_function + aws_region: us-west-2 + - name: cors + config: + origins: + - example.com + methods: + - GET + - POST + headers: + - Authorization + exposed_headers: + - X-My-Header + max_age: 3600 + credentials: true + - name: file-log + config: + path: /var/log/kong/kong.log + reopen: true + - name: http-log + config: + http_endpoint: http://example.com/logs + method: POST + content_type: application/json + timeout: 10000 + keepalive: 60000 + retry_count: 10 + queue_size: 1000 + - name: ip-restriction + config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 + - name: rate-limiting-advanced + config: + limit: + - 5 + window_size: + - 30 + identifier: consumer + sync_rate: -1 + namespace: example_namespace + strategy: local + hide_client_headers: false + - name: request-termination + config: + status_code: 403 + message: Forbidden + - name: response-ratelimiting + config: + limits: + limit_name: + minute: 10 + policy: local + - name: tcp-log + config: + host: example.com + port: 1234 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/output-expected.yaml new file mode 100644 index 000000000..1d6ae5982 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/10-mulitple-plugins-same-route/output-expected.yaml @@ -0,0 +1,292 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + aws_key: my_key + aws_region: us-west-2 + aws_secret: my_secret + function_name: my_function +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: aws-lambda +plugin: aws-lambda +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + credentials: true + exposed_headers: + - X-My-Header + headers: + - Authorization + max_age: 3600 + methods: + - GET + - POST + origins: + - example.com +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: cors +plugin: cors +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + path: /var/log/kong/kong.log + reopen: true +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: file-log +plugin: file-log +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + content_type: application/json + http_endpoint: http://example.com/logs + keepalive: 60000 + method: POST + queue_size: 1000 + retry_count: 10 + timeout: 10000 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: http-log +plugin: http-log +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: ip-restriction +plugin: ip-restriction +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_client_headers: false + identifier: consumer + limit: + - 5 + namespace: example_namespace + strategy: local + sync_rate: -1 + window_size: + - 30 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: rate-limiting-advanced +plugin: rate-limiting-advanced +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + message: Forbidden + status_code: 403 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: request-termination +plugin: request-termination +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + limits: + limit_name: + minute: 10 + policy: local +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: response-ratelimiting +plugin: response-ratelimiting +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + host: example.com + port: 1234 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: tcp-log +plugin: tcp-log +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: aws-lambda,cors,file-log,http-log,ip-restriction,rate-limiting-advanced,request-termination,response-ratelimiting,tcp-log + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: configuration.konghq.com/v1 +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/annotations/yaml/11-consumer-group/input.yaml b/file/testdata/kong2kic/annotations/yaml/11-consumer-group/input.yaml new file mode 100644 index 000000000..b57971e33 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/11-consumer-group/input.yaml @@ -0,0 +1,9 @@ +consumer_groups: + - name: example-consumer-group + consumers: + - username: example-user +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/11-consumer-group/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/11-consumer-group/output-expected.yaml new file mode 100644 index 000000000..19827ed76 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/11-consumer-group/output-expected.yaml @@ -0,0 +1,22 @@ +apiVersion: configuration.konghq.com/v1 +consumerGroups: +- example-consumer-group +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- +apiVersion: configuration.konghq.com/v1 +kind: KongConsumerGroup +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-consumer-group +status: {} +--- diff --git a/file/testdata/kong2kic/annotations/yaml/2-service-and-route/input.yaml b/file/testdata/kong2kic/annotations/yaml/2-service-and-route/input.yaml new file mode 100644 index 000000000..96ffcce6e --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/2-service-and-route/input.yaml @@ -0,0 +1,44 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/2-service-and-route/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/2-service-and-route/output-expected.yaml new file mode 100644 index 000000000..a69aee1d9 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/2-service-and-route/output-expected.yaml @@ -0,0 +1,53 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/input.yaml b/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/input.yaml new file mode 100644 index 000000000..0ee8eeadd --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/input.yaml @@ -0,0 +1,97 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/output-expected.yaml new file mode 100644 index 000000000..017f8f2d3 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/3-service-and-upstream/output-expected.yaml @@ -0,0 +1,105 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/input.yaml b/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/input.yaml new file mode 100644 index 000000000..9e4e5ec86 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/input.yaml @@ -0,0 +1,125 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/output-expected.yaml new file mode 100644 index 000000000..9baddd252 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/4-service-route-upstream/output-expected.yaml @@ -0,0 +1,136 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/input.yaml b/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/input.yaml new file mode 100644 index 000000000..e9fb52034 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: acl + config: + allow: + - admin +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + acls: + - group: acl_group + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/output-expected.yaml new file mode 100644 index 000000000..dc025e73b --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/5-service-route-upstream-acl-auth/output-expected.yaml @@ -0,0 +1,174 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + allow: + - admin +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: acl +plugin: acl +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: acl + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: acl-group-example-user +stringData: + group: acl_group + kongCredType: acl +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- acl-group-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/input.yaml b/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/input.yaml new file mode 100644 index 000000000..163846d4b --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: basic-auth + config: + hide_credentials: false +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + basicauth_credentials: + - username: my_basic_user + password: my_basic_password + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/output-expected.yaml new file mode 100644 index 000000000..918b21b62 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/6-service-route-upstream-basic-auth/output-expected.yaml @@ -0,0 +1,174 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_credentials: false +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: basic-auth +plugin: basic-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: basic-auth + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: basic-auth-example-user +stringData: + kongCredType: basic-auth + password: my_basic_password + username: my_basic_user +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- basic-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/input.yaml b/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/input.yaml new file mode 100644 index 000000000..363f14ea3 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/input.yaml @@ -0,0 +1,151 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: jwt + config: + uri_param_names: + - token + claims_to_verify: + - exp + - nbf + key_claim_name: kid + secret_is_base64: false + anonymous: null + run_on_preflight: true + maximum_expiration: 3600 + header_names: + - Authorization +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + jwt_secrets: + - key: my_jwt_secret + algorithm: HS256 + secret: my_secret_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml new file mode 100644 index 000000000..56265f57b --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml @@ -0,0 +1,186 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + anonymous: null + claims_to_verify: + - exp + - nbf + header_names: + - Authorization + key_claim_name: kid + maximum_expiration: 3600 + run_on_preflight: true + secret_is_base64: false + uri_param_names: + - token +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: jwt +plugin: jwt +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: jwt + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: jwt-auth-example-user +stringData: + algorithm: HS256 + key: my_jwt_secret + kongCredType: jwt + secret: my_secret_key +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- jwt-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/input.yaml b/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/input.yaml new file mode 100644 index 000000000..754bb520c --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/input.yaml @@ -0,0 +1,142 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: key-auth + config: + hide_credentials: false + key_names: + - apikey + key_in_body: false + run_on_preflight: true +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + keyauth_credentials: + - key: my_api_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/output-expected.yaml new file mode 100644 index 000000000..076148230 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/8-service-route-upstream-key-auth/output-expected.yaml @@ -0,0 +1,177 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_credentials: false + key_in_body: false + key_names: + - apikey + run_on_preflight: true +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: key-auth +plugin: key-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: key-auth + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: key-auth-example-user +stringData: + key: my_api_key + kongCredType: key-auth +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- key-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/input.yaml b/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/input.yaml new file mode 100644 index 000000000..f12055648 --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/input.yaml @@ -0,0 +1,140 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: mtls-auth + config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + skip_consumer_lookup: false + revocation_check_mode: SKIP +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + mtls_auth_credentials: + - id: cce8c384-721f-4f58-85dd-50834e3e733a + subject_name: example-user@example.com \ No newline at end of file diff --git a/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml b/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml new file mode 100644 index 000000000..640e3615b --- /dev/null +++ b/file/testdata/kong2kic/annotations/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml @@ -0,0 +1,178 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + revocation_check_mode: SKIP + skip_consumer_lookup: false +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: mtls-auth +plugin: mtls-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/headers.x-another-header: bla + konghq.com/headers.x-my-header: foo,bar + konghq.com/https-redirect-status-code: "302" + konghq.com/methods: GET,POST + konghq.com/plugins: mtls-auth + konghq.com/preserve-host: "true" + konghq.com/protocols: http,https + konghq.com/regex-priority: "1" + konghq.com/snis: example.com + konghq.com/strip-path: "false" + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/connect-timeout: "5000" + konghq.com/override: example-service + konghq.com/path: /v1 + konghq.com/protocol: http + konghq.com/read-timeout: "60000" + konghq.com/retries: "5" + konghq.com/write-timeout: "60000" + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: mtls-auth-example-user +stringData: + id: cce8c384-721f-4f58-85dd-50834e3e733a + kongCredType: mtls-auth + subject_name: example-user@example.com +type: Opaque +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- mtls-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/json/1-service/input.yaml b/file/testdata/kong2kic/custom_resources/json/1-service/input.yaml new file mode 100644 index 000000000..2632c2c53 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/1-service/input.yaml @@ -0,0 +1,16 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/1-service/output-expected.json b/file/testdata/kong2kic/custom_resources/json/1-service/output-expected.json new file mode 100644 index 000000000..db9c52653 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/1-service/output-expected.json @@ -0,0 +1,44 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/input.yaml b/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/input.yaml new file mode 100644 index 000000000..316ff1b35 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/input.yaml @@ -0,0 +1,194 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: aws-lambda + config: + aws_key: my_key + aws_secret: my_secret + function_name: my_function + aws_region: us-west-2 + - name: cors + config: + origins: + - example.com + methods: + - GET + - POST + headers: + - Authorization + exposed_headers: + - X-My-Header + max_age: 3600 + credentials: true + - name: file-log + config: + path: /var/log/kong/kong.log + reopen: true + - name: http-log + config: + http_endpoint: http://example.com/logs + method: POST + content_type: application/json + timeout: 10000 + keepalive: 60000 + retry_count: 10 + queue_size: 1000 + - name: ip-restriction + config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 + - name: rate-limiting-advanced + config: + limit: + - 5 + window_size: + - 30 + identifier: consumer + sync_rate: -1 + namespace: example_namespace + strategy: local + hide_client_headers: false + - name: request-termination + config: + status_code: 403 + message: Forbidden + - name: response-ratelimiting + config: + limits: + limit_name: + minute: 10 + policy: local + - name: tcp-log + config: + host: example.com + port: 1234 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/output-expected.json b/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/output-expected.json new file mode 100644 index 000000000..152f3b8ae --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/10-mulitple-plugins-same-route/output-expected.json @@ -0,0 +1,401 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "aws-lambda", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "aws_key": "my_key", + "aws_region": "us-west-2", + "aws_secret": "my_secret", + "function_name": "my_function" + }, + "plugin": "aws-lambda", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "cors", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "credentials": true, + "exposed_headers": [ + "X-My-Header" + ], + "headers": [ + "Authorization" + ], + "max_age": 3600, + "methods": [ + "GET", + "POST" + ], + "origins": [ + "example.com" + ] + }, + "plugin": "cors", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "file-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "path": "/var/log/kong/kong.log", + "reopen": true + }, + "plugin": "file-log", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "http-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "content_type": "application/json", + "http_endpoint": "http://example.com/logs", + "keepalive": 60000, + "method": "POST", + "queue_size": 1000, + "retry_count": 10, + "timeout": 10000 + }, + "plugin": "http-log", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "ip-restriction", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "allow": [ + "192.168.0.1/24" + ], + "deny": [ + "192.168.0.2/32" + ] + }, + "plugin": "ip-restriction", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "rate-limiting-advanced", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_client_headers": false, + "identifier": "consumer", + "limit": [ + 5 + ], + "namespace": "example_namespace", + "strategy": "local", + "sync_rate": -1, + "window_size": [ + 30 + ] + }, + "plugin": "rate-limiting-advanced", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "request-termination", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "message": "Forbidden", + "status_code": 403 + }, + "plugin": "request-termination", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "response-ratelimiting", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "limits": { + "limit_name": { + "minute": 10 + } + }, + "policy": "local" + }, + "plugin": "response-ratelimiting", + "status": {} +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "tcp-log", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "host": "example.com", + "port": 1234 + }, + "plugin": "tcp-log", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "aws-lambda,cors,file-log,http-log,ip-restriction,rate-limiting-advanced,request-termination,response-ratelimiting,tcp-log" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/11-consumer-group/input.yaml b/file/testdata/kong2kic/custom_resources/json/11-consumer-group/input.yaml new file mode 100644 index 000000000..b57971e33 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/11-consumer-group/input.yaml @@ -0,0 +1,9 @@ +consumer_groups: + - name: example-consumer-group + consumers: + - username: example-user +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/11-consumer-group/output-expected.json b/file/testdata/kong2kic/custom_resources/json/11-consumer-group/output-expected.json new file mode 100644 index 000000000..3e8cf4b1e --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/11-consumer-group/output-expected.json @@ -0,0 +1,28 @@ +{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "consumerGroups": [ + "example-consumer-group" + ], + "status": {} +}{ + "kind": "KongConsumerGroup", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-consumer-group", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/2-service-and-route/input.yaml b/file/testdata/kong2kic/custom_resources/json/2-service-and-route/input.yaml new file mode 100644 index 000000000..96ffcce6e --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/2-service-and-route/input.yaml @@ -0,0 +1,44 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/2-service-and-route/output-expected.json b/file/testdata/kong2kic/custom_resources/json/2-service-and-route/output-expected.json new file mode 100644 index 000000000..86ae5d069 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/2-service-and-route/output-expected.json @@ -0,0 +1,117 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/input.yaml b/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/input.yaml new file mode 100644 index 000000000..0ee8eeadd --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/input.yaml @@ -0,0 +1,97 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/output-expected.json b/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/output-expected.json new file mode 100644 index 000000000..f226a23db --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/3-service-and-upstream/output-expected.json @@ -0,0 +1,133 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/input.yaml b/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/input.yaml new file mode 100644 index 000000000..9e4e5ec86 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/input.yaml @@ -0,0 +1,125 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/output-expected.json b/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/output-expected.json new file mode 100644 index 000000000..23d2b86d8 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/4-service-route-upstream/output-expected.json @@ -0,0 +1,206 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/input.yaml b/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/input.yaml new file mode 100644 index 000000000..e9fb52034 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: acl + config: + allow: + - admin +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + acls: + - group: acl_group + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/output-expected.json b/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/output-expected.json new file mode 100644 index 000000000..e2e70f0b5 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/5-service-route-upstream-acl-auth/output-expected.json @@ -0,0 +1,254 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "acl", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "allow": [ + "admin" + ] + }, + "plugin": "acl", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "acl" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "acl-group-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "group": "acl_group", + "kongCredType": "acl" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "acl-group-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/input.yaml b/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/input.yaml new file mode 100644 index 000000000..163846d4b --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: basic-auth + config: + hide_credentials: false +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + basicauth_credentials: + - username: my_basic_user + password: my_basic_password + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/output-expected.json b/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/output-expected.json new file mode 100644 index 000000000..0701fbb57 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/6-service-route-upstream-basic-auth/output-expected.json @@ -0,0 +1,253 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "basic-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_credentials": false + }, + "plugin": "basic-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "basic-auth" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "basic-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "kongCredType": "basic-auth", + "password": "my_basic_password", + "username": "my_basic_user" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "basic-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/input.yaml b/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/input.yaml new file mode 100644 index 000000000..363f14ea3 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/input.yaml @@ -0,0 +1,151 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: jwt + config: + uri_param_names: + - token + claims_to_verify: + - exp + - nbf + key_claim_name: kid + secret_is_base64: false + anonymous: null + run_on_preflight: true + maximum_expiration: 3600 + header_names: + - Authorization +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + jwt_secrets: + - key: my_jwt_secret + algorithm: HS256 + secret: my_secret_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/output-expected.json b/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/output-expected.json new file mode 100644 index 000000000..035d62dbf --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/7-service-route-upstream-jwt-auth/output-expected.json @@ -0,0 +1,268 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "jwt", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "anonymous": null, + "claims_to_verify": [ + "exp", + "nbf" + ], + "header_names": [ + "Authorization" + ], + "key_claim_name": "kid", + "maximum_expiration": 3600, + "run_on_preflight": true, + "secret_is_base64": false, + "uri_param_names": [ + "token" + ] + }, + "plugin": "jwt", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "jwt" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "jwt-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "algorithm": "HS256", + "key": "my_jwt_secret", + "kongCredType": "jwt", + "secret": "my_secret_key" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "jwt-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/input.yaml b/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/input.yaml new file mode 100644 index 000000000..754bb520c --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/input.yaml @@ -0,0 +1,142 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: key-auth + config: + hide_credentials: false + key_names: + - apikey + key_in_body: false + run_on_preflight: true +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + keyauth_credentials: + - key: my_api_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/output-expected.json b/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/output-expected.json new file mode 100644 index 000000000..59f3c4dc2 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/8-service-route-upstream-key-auth/output-expected.json @@ -0,0 +1,257 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "key-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "hide_credentials": false, + "key_in_body": false, + "key_names": [ + "apikey" + ], + "run_on_preflight": true + }, + "plugin": "key-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "key-auth" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "key-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "key": "my_api_key", + "kongCredType": "key-auth" + } +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "key-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/input.yaml b/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/input.yaml new file mode 100644 index 000000000..f12055648 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/input.yaml @@ -0,0 +1,140 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: mtls-auth + config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + skip_consumer_lookup: false + revocation_check_mode: SKIP +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + mtls_auth_credentials: + - id: cce8c384-721f-4f58-85dd-50834e3e733a + subject_name: example-user@example.com \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/output-expected.json b/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/output-expected.json new file mode 100644 index 000000000..22c01e94f --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/json/9-service-route-upstream-mtls-auth/output-expected.json @@ -0,0 +1,258 @@ +{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "upstream": { + "host_header": "example.com", + "algorithm": "round-robin", + "slots": 10000, + "healthchecks": { + "active": { + "concurrency": 10, + "healthy": { + "http_statuses": [ + 200, + 302 + ], + "interval": 0, + "successes": 0 + }, + "http_path": "/", + "https_sni": "example.com", + "https_verify_certificate": true, + "type": "http", + "timeout": 1, + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 404, + 500, + 501, + 502, + 503, + 504, + 505 + ], + "tcp_failures": 0, + "timeouts": 0, + "interval": 0 + }, + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + } + }, + "passive": { + "healthy": { + "http_statuses": [ + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 226, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308 + ], + "successes": 0 + }, + "type": "http", + "unhealthy": { + "http_failures": 0, + "http_statuses": [ + 429, + 500, + 503 + ], + "tcp_failures": 0, + "timeouts": 0 + } + }, + "threshold": 0 + }, + "hash_on": "none", + "hash_fallback": "none", + "hash_on_cookie_path": "/" + }, + "proxy": { + "protocol": "http", + "path": "/v1", + "retries": 5, + "connect_timeout": 5000, + "read_timeout": 60000, + "write_timeout": 60000 + } +}{ + "kind": "KongIngress", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "route": { + "methods": [ + "GET", + "POST" + ], + "headers": { + "x-another-header": [ + "bla" + ], + "x-my-header": [ + "foo", + "bar" + ] + }, + "protocols": [ + "http", + "https" + ], + "regex_priority": 1, + "strip_path": false, + "preserve_host": true, + "https_redirect_status_code": 302, + "snis": [ + "example.com" + ] + } +}{ + "kind": "KongPlugin", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "mtls-auth", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "config": { + "ca_certificates": [ + "cce8c384-721f-4f58-85dd-50834e3e733a" + ], + "revocation_check_mode": "SKIP", + "skip_consumer_lookup": false + }, + "plugin": "mtls-auth", + "status": {} +}{ + "kind": "Ingress", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "example-route", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-route", + "konghq.com/plugins": "mtls-auth" + } + }, + "spec": { + "ingressClassName": "kong", + "rules": [ + { + "host": "example.com", + "http": { + "paths": [ + { + "path": "/v1/example", + "pathType": "ImplementationSpecific", + "backend": { + "service": { + "name": "example-service", + "port": { + "number": 80 + } + } + } + } + ] + } + } + ] + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "example-service", + "creationTimestamp": null, + "annotations": { + "konghq.com/override": "example-service" + } + }, + "spec": { + "ports": [ + { + "protocol": "TCP", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "app": "example-service" + } + }, + "status": { + "loadBalancer": {} + } +}{ + "kind": "Secret", + "apiVersion": "v1", + "metadata": { + "name": "mtls-auth-example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "stringData": { + "id": "cce8c384-721f-4f58-85dd-50834e3e733a", + "kongCredType": "mtls-auth", + "subject_name": "example-user@example.com" + }, + "type": "Opaque" +}{ + "kind": "KongConsumer", + "apiVersion": "configuration.konghq.com/v1", + "metadata": { + "name": "example-user", + "creationTimestamp": null, + "annotations": { + "kubernetes.io/ingress.class": "kong" + } + }, + "username": "example-user", + "custom_id": "1234567890", + "credentials": [ + "mtls-auth-example-user" + ], + "status": {} +} \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/1-service/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/1-service/input.yaml new file mode 100644 index 000000000..2632c2c53 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/1-service/input.yaml @@ -0,0 +1,16 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/1-service/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/1-service/output-expected.yaml new file mode 100644 index 000000000..14aad8740 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/1-service/output-expected.yaml @@ -0,0 +1,32 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/input.yaml new file mode 100644 index 000000000..316ff1b35 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/input.yaml @@ -0,0 +1,194 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: aws-lambda + config: + aws_key: my_key + aws_secret: my_secret + function_name: my_function + aws_region: us-west-2 + - name: cors + config: + origins: + - example.com + methods: + - GET + - POST + headers: + - Authorization + exposed_headers: + - X-My-Header + max_age: 3600 + credentials: true + - name: file-log + config: + path: /var/log/kong/kong.log + reopen: true + - name: http-log + config: + http_endpoint: http://example.com/logs + method: POST + content_type: application/json + timeout: 10000 + keepalive: 60000 + retry_count: 10 + queue_size: 1000 + - name: ip-restriction + config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 + - name: rate-limiting-advanced + config: + limit: + - 5 + window_size: + - 30 + identifier: consumer + sync_rate: -1 + namespace: example_namespace + strategy: local + hide_client_headers: false + - name: request-termination + config: + status_code: 403 + message: Forbidden + - name: response-ratelimiting + config: + limits: + limit_name: + minute: 10 + policy: local + - name: tcp-log + config: + host: example.com + port: 1234 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/output-expected.yaml new file mode 100644 index 000000000..b951ff177 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/10-mulitple-plugins-same-route/output-expected.yaml @@ -0,0 +1,312 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + aws_key: my_key + aws_region: us-west-2 + aws_secret: my_secret + function_name: my_function +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: aws-lambda +plugin: aws-lambda +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + credentials: true + exposed_headers: + - X-My-Header + headers: + - Authorization + max_age: 3600 + methods: + - GET + - POST + origins: + - example.com +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: cors +plugin: cors +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + path: /var/log/kong/kong.log + reopen: true +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: file-log +plugin: file-log +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + content_type: application/json + http_endpoint: http://example.com/logs + keepalive: 60000 + method: POST + queue_size: 1000 + retry_count: 10 + timeout: 10000 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: http-log +plugin: http-log +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + allow: + - 192.168.0.1/24 + deny: + - 192.168.0.2/32 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: ip-restriction +plugin: ip-restriction +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_client_headers: false + identifier: consumer + limit: + - 5 + namespace: example_namespace + strategy: local + sync_rate: -1 + window_size: + - 30 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: rate-limiting-advanced +plugin: rate-limiting-advanced +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + message: Forbidden + status_code: 403 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: request-termination +plugin: request-termination +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + limits: + limit_name: + minute: 10 + policy: local +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: response-ratelimiting +plugin: response-ratelimiting +status: {} +--- +apiVersion: configuration.konghq.com/v1 +config: + host: example.com + port: 1234 +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: tcp-log +plugin: tcp-log +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: aws-lambda,cors,file-log,http-log,ip-restriction,rate-limiting-advanced,request-termination,response-ratelimiting,tcp-log + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: configuration.konghq.com/v1 +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/input.yaml new file mode 100644 index 000000000..b57971e33 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/input.yaml @@ -0,0 +1,9 @@ +consumer_groups: + - name: example-consumer-group + consumers: + - username: example-user +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/output-expected.yaml new file mode 100644 index 000000000..19827ed76 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/11-consumer-group/output-expected.yaml @@ -0,0 +1,22 @@ +apiVersion: configuration.konghq.com/v1 +consumerGroups: +- example-consumer-group +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- +apiVersion: configuration.konghq.com/v1 +kind: KongConsumerGroup +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-consumer-group +status: {} +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/input.yaml new file mode 100644 index 000000000..96ffcce6e --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/input.yaml @@ -0,0 +1,44 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/output-expected.yaml new file mode 100644 index 000000000..9d64b3a1d --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/2-service-and-route/output-expected.yaml @@ -0,0 +1,82 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/input.yaml new file mode 100644 index 000000000..0ee8eeadd --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/input.yaml @@ -0,0 +1,97 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/output-expected.yaml new file mode 100644 index 000000000..f53030b9c --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/3-service-and-upstream/output-expected.yaml @@ -0,0 +1,106 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/input.yaml new file mode 100644 index 000000000..9e4e5ec86 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/input.yaml @@ -0,0 +1,125 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false diff --git a/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/output-expected.yaml new file mode 100644 index 000000000..7092e95a8 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/4-service-route-upstream/output-expected.yaml @@ -0,0 +1,156 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/input.yaml new file mode 100644 index 000000000..e9fb52034 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: acl + config: + allow: + - admin +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + acls: + - group: acl_group + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/output-expected.yaml new file mode 100644 index 000000000..e9d346443 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/5-service-route-upstream-acl-auth/output-expected.yaml @@ -0,0 +1,194 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + allow: + - admin +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: acl +plugin: acl +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: acl + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: acl-group-example-user +stringData: + group: acl_group + kongCredType: acl +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- acl-group-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/input.yaml new file mode 100644 index 000000000..163846d4b --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/input.yaml @@ -0,0 +1,139 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: basic-auth + config: + hide_credentials: false +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + basicauth_credentials: + - username: my_basic_user + password: my_basic_password + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/output-expected.yaml new file mode 100644 index 000000000..023b013c4 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/6-service-route-upstream-basic-auth/output-expected.yaml @@ -0,0 +1,194 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_credentials: false +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: basic-auth +plugin: basic-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: basic-auth + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: basic-auth-example-user +stringData: + kongCredType: basic-auth + password: my_basic_password + username: my_basic_user +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- basic-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/input.yaml new file mode 100644 index 000000000..363f14ea3 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/input.yaml @@ -0,0 +1,151 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: jwt + config: + uri_param_names: + - token + claims_to_verify: + - exp + - nbf + key_claim_name: kid + secret_is_base64: false + anonymous: null + run_on_preflight: true + maximum_expiration: 3600 + header_names: + - Authorization +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + jwt_secrets: + - key: my_jwt_secret + algorithm: HS256 + secret: my_secret_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml new file mode 100644 index 000000000..f0861819f --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/7-service-route-upstream-jwt-auth/output-expected.yaml @@ -0,0 +1,206 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + anonymous: null + claims_to_verify: + - exp + - nbf + header_names: + - Authorization + key_claim_name: kid + maximum_expiration: 3600 + run_on_preflight: true + secret_is_base64: false + uri_param_names: + - token +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: jwt +plugin: jwt +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: jwt + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: jwt-auth-example-user +stringData: + algorithm: HS256 + key: my_jwt_secret + kongCredType: jwt + secret: my_secret_key +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- jwt-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/input.yaml new file mode 100644 index 000000000..754bb520c --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/input.yaml @@ -0,0 +1,142 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: key-auth + config: + hide_credentials: false + key_names: + - apikey + key_in_body: false + run_on_preflight: true +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + keyauth_credentials: + - key: my_api_key + tags: + - internal \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/output-expected.yaml new file mode 100644 index 000000000..c28d85653 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/8-service-route-upstream-key-auth/output-expected.yaml @@ -0,0 +1,197 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + hide_credentials: false + key_in_body: false + key_names: + - apikey + run_on_preflight: true +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: key-auth +plugin: key-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: key-auth + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: key-auth-example-user +stringData: + key: my_api_key + kongCredType: key-auth +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- key-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/input.yaml b/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/input.yaml new file mode 100644 index 000000000..f12055648 --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/input.yaml @@ -0,0 +1,140 @@ +services: + - name: example-service + url: http://example-api.com + protocol: http + host: example-api.com + port: 80 + path: /v1 + retries: 5 + connect_timeout: 5000 + write_timeout: 60000 + read_timeout: 60000 + enabled: true + client_certificate: 4e3ad2e4-0bc4-4638-8e34-c84a417ba39b + tags: + - example + - api + routes: + - name: example-route + methods: + - GET + - POST + hosts: + - example.com + paths: + - /v1/example + protocols: + - http + - https + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + regex_priority: 1 + strip_path: false + preserve_host: true + tags: + - version:v1 + https_redirect_status_code: 302 + snis: + - example.com + sources: + - ip: 192.168.0.1 + plugins: + - name: mtls-auth + config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + skip_consumer_lookup: false + revocation_check_mode: SKIP +upstreams: + - name: example-api.com + algorithm: round-robin + hash_on: none + hash_fallback: none + hash_on_cookie_path: "/" + slots: 10000 + healthchecks: + passive: + type: http + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + unhealthy: + http_statuses: + - 429 + - 500 + - 503 + timeouts: 0 + http_failures: 0 + tcp_failures: 0 + active: + https_verify_certificate: true + healthy: + http_statuses: + - 200 + - 302 + successes: 0 + interval: 0 + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + timeouts: 0 + tcp_failures: 0 + interval: 0 + type: http + concurrency: 10 + headers: + x-my-header: + - foo + - bar + x-another-header: + - bla + timeout: 1 + http_path: "/" + https_sni: example.com + threshold: 0 + tags: + - user-level + - low-priority + host_header: example.com + client_certificate: + id: ea29aaa3-3b2d-488c-b90c-56df8e0dd8c6 + use_srv_name: false +consumers: + - username: example-user + custom_id: "1234567890" + tags: + - internal + mtls_auth_credentials: + - id: cce8c384-721f-4f58-85dd-50834e3e733a + subject_name: example-user@example.com \ No newline at end of file diff --git a/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml b/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml new file mode 100644 index 000000000..01bebbf8e --- /dev/null +++ b/file/testdata/kong2kic/custom_resources/yaml/9-service-route-upstream-mtls-auth/output-expected.yaml @@ -0,0 +1,198 @@ +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-service +proxy: + connect_timeout: 5000 + path: /v1 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 +upstream: + algorithm: round-robin + hash_fallback: none + hash_on: none + hash_on_cookie_path: / + healthchecks: + active: + concurrency: 10 + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + healthy: + http_statuses: + - 200 + - 302 + interval: 0 + successes: 0 + http_path: / + https_sni: example.com + https_verify_certificate: true + timeout: 1 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 404 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + interval: 0 + tcp_failures: 0 + timeouts: 0 + passive: + healthy: + http_statuses: + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 226 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + successes: 0 + type: http + unhealthy: + http_failures: 0 + http_statuses: + - 429 + - 500 + - 503 + tcp_failures: 0 + timeouts: 0 + threshold: 0 + host_header: example.com + slots: 10000 +--- +apiVersion: configuration.konghq.com/v1 +kind: KongIngress +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-route +route: + headers: + x-another-header: + - bla + x-my-header: + - foo + - bar + https_redirect_status_code: 302 + methods: + - GET + - POST + preserve_host: true + protocols: + - http + - https + regex_priority: 1 + snis: + - example.com + strip_path: false +--- +apiVersion: configuration.konghq.com/v1 +config: + ca_certificates: + - cce8c384-721f-4f58-85dd-50834e3e733a + revocation_check_mode: SKIP + skip_consumer_lookup: false +kind: KongPlugin +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: mtls-auth +plugin: mtls-auth +status: {} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + konghq.com/override: example-route + konghq.com/plugins: mtls-auth + creationTimestamp: null + name: example-route +spec: + ingressClassName: kong + rules: + - host: example.com + http: + paths: + - backend: + service: + name: example-service + port: + number: 80 + path: /v1/example + pathType: ImplementationSpecific +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + konghq.com/override: example-service + creationTimestamp: null + name: example-service +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: example-service +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: mtls-auth-example-user +stringData: + id: cce8c384-721f-4f58-85dd-50834e3e733a + kongCredType: mtls-auth + subject_name: example-user@example.com +type: Opaque +--- +apiVersion: configuration.konghq.com/v1 +credentials: +- mtls-auth-example-user +custom_id: "1234567890" +kind: KongConsumer +metadata: + annotations: + kubernetes.io/ingress.class: kong + creationTimestamp: null + name: example-user +status: {} +username: example-user +--- diff --git a/file/types.go b/file/types.go index 7c6aa44c5..d778ad5e5 100644 --- a/file/types.go +++ b/file/types.go @@ -9,6 +9,11 @@ import ( "github.com/kong/deck/utils" "github.com/kong/go-kong/kong" + kicv1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1" + kicv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1" + k8scorev1 "k8s.io/api/core/v1" + k8snetv1 "k8s.io/api/networking/v1" + "sigs.k8s.io/yaml" ) // Format is a file format for Kong's configuration. @@ -23,6 +28,11 @@ const ( JSON = "JSON" // YAML if YAML file format. YAML = "YAML" + // KIC YAML and JSON file format for ingress controller. + KIC_YAML_CRD = "KIC_YAML_CRD" + KIC_JSON_CRD = "KIC_JSON_CRD" + KIC_YAML_ANNOTATION = "KIC_YAML_ANNOTATION" + KIC_JSON_ANNOTATION = "KIC_JSON_ANNOTATION" ) const ( @@ -321,19 +331,20 @@ type FPlugin struct { // foo is a shadow type of Plugin. // It is used for custom marshalling of plugin. type foo struct { - CreatedAt *int `json:"created_at,omitempty" yaml:"created_at,omitempty"` - ID *string `json:"id,omitempty" yaml:"id,omitempty"` - Name *string `json:"name,omitempty" yaml:"name,omitempty"` - InstanceName *string `json:"instance_name,omitempty" yaml:"instance_name,omitempty"` - Config kong.Configuration `json:"config,omitempty" yaml:"config,omitempty"` - Service string `json:"service,omitempty" yaml:",omitempty"` - Consumer string `json:"consumer,omitempty" yaml:",omitempty"` - Route string `json:"route,omitempty" yaml:",omitempty"` - Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` - RunOn *string `json:"run_on,omitempty" yaml:"run_on,omitempty"` - Ordering *kong.PluginOrdering `json:"ordering,omitempty" yaml:"ordering,omitempty"` - Protocols []*string `json:"protocols,omitempty" yaml:"protocols,omitempty"` - Tags []*string `json:"tags,omitempty" yaml:"tags,omitempty"` + CreatedAt *int `json:"created_at,omitempty" yaml:"created_at,omitempty"` + ID *string `json:"id,omitempty" yaml:"id,omitempty"` + Name *string `json:"name,omitempty" yaml:"name,omitempty"` + InstanceName *string `json:"instance_name,omitempty" yaml:"instance_name,omitempty"` + Config kong.Configuration `json:"config,omitempty" yaml:"config,omitempty"` + Service string `json:"service,omitempty" yaml:",omitempty"` + Consumer string `json:"consumer,omitempty" yaml:",omitempty"` + ConsumerGroup string `json:"consumer_group,omitempty" yaml:",omitempty"` + Route string `json:"route,omitempty" yaml:",omitempty"` + Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + RunOn *string `json:"run_on,omitempty" yaml:"run_on,omitempty"` + Ordering *kong.PluginOrdering `json:"ordering,omitempty" yaml:"ordering,omitempty"` + Protocols []*string `json:"protocols,omitempty" yaml:"protocols,omitempty"` + Tags []*string `json:"tags,omitempty" yaml:"tags,omitempty"` ConfigSource *string `json:"_config,omitempty" yaml:"_config,omitempty"` } @@ -379,6 +390,9 @@ func copyToFoo(p FPlugin) foo { if p.Plugin.Service != nil { f.Service = *p.Plugin.Service.ID } + if p.Plugin.ConsumerGroup != nil { + f.ConsumerGroup = *p.Plugin.ConsumerGroup.ID + } return f } @@ -428,6 +442,11 @@ func copyFromFoo(f foo, p *FPlugin) { ID: kong.String(f.Service), } } + if f.ConsumerGroup != "" { + p.ConsumerGroup = &kong.ConsumerGroup{ + ID: kong.String(f.ConsumerGroup), + } + } } // MarshalYAML is a custom marshal method to handle @@ -480,6 +499,9 @@ func (p FPlugin) sortKey() string { if p.Service != nil { key += *p.Service.ID } + if p.ConsumerGroup != nil { + key += *p.ConsumerGroup.ID + } return key } if p.ID != nil { @@ -728,3 +750,201 @@ type Content struct { Licenses []FLicense `json:"licenses,omitempty" yaml:"licenses,omitempty"` } + +// KICContent represents a serialized Kong state for KIC. +// +k8s:deepcopy-gen=true +type KICContent struct { + KongIngresses []kicv1.KongIngress `json:"kongIngresses,omitempty" yaml:",omitempty"` + KongPlugins []kicv1.KongPlugin `json:"kongPlugins,omitempty" yaml:",omitempty"` + KongClusterPlugins []kicv1.KongClusterPlugin `json:"clusterPlugins,omitempty" yaml:",omitempty"` + Ingresses []k8snetv1.Ingress `json:"ingresses,omitempty" yaml:",omitempty"` + Services []k8scorev1.Service `json:"services,omitempty" yaml:",omitempty"` + Secrets []k8scorev1.Secret `json:"secrets,omitempty" yaml:",omitempty"` + KongConsumers []kicv1.KongConsumer `json:"consumers,omitempty" yaml:",omitempty"` + KongConsumerGroups []kicv1beta1.KongConsumerGroup `json:"consumerGroups,omitempty" yaml:",omitempty"` +} + +func (k KICContent) marshalKICContentToYaml() ([]byte, error) { + + var kongIngresses []byte + var kongPlugins []byte + var kongClusterPlugins []byte + var ingresses []byte + var services []byte + var secrets []byte + var kongConsumers []byte + var kongConsumerGroups []byte + var err error + var output []byte + + // iterate over the slices of kongIngresses, kongPlugins, + // kongClusterPlugins, ingresses, services, secrets, kongConsumers + // and marshal each one in yaml format + // and append it to the output slice + // then return the output slice + for _, kongIngress := range k.KongIngresses { + kongIngresses, err = yaml.Marshal(kongIngress) + if err != nil { + return nil, err + } + output = append(output, kongIngresses...) + output = append(output, []byte("---\n")...) + } + + for _, kongPlugin := range k.KongPlugins { + kongPlugins, err = yaml.Marshal(kongPlugin) + if err != nil { + return nil, err + } + output = append(output, kongPlugins...) + output = append(output, []byte("---\n")...) + + } + + for _, kongClusterPlugin := range k.KongClusterPlugins { + kongClusterPlugins, err = yaml.Marshal(kongClusterPlugin) + if err != nil { + return nil, err + } + output = append(output, kongClusterPlugins...) + output = append(output, []byte("---\n")...) + + } + + for _, ingress := range k.Ingresses { + ingresses, err = yaml.Marshal(ingress) + if err != nil { + return nil, err + } + output = append(output, ingresses...) + output = append(output, []byte("---\n")...) + + } + + for _, service := range k.Services { + services, err = yaml.Marshal(service) + if err != nil { + return nil, err + } + output = append(output, services...) + output = append(output, []byte("---\n")...) + + } + + for _, secret := range k.Secrets { + secrets, err = yaml.Marshal(secret) + if err != nil { + return nil, err + } + output = append(output, secrets...) + output = append(output, []byte("---\n")...) + + } + + for _, kongConsumer := range k.KongConsumers { + kongConsumers, err = yaml.Marshal(kongConsumer) + if err != nil { + return nil, err + } + output = append(output, kongConsumers...) + output = append(output, []byte("---\n")...) + + } + + for _, kongConsumerGroup := range k.KongConsumerGroups { + kongConsumerGroups, err = yaml.Marshal(kongConsumerGroup) + if err != nil { + return nil, err + } + output = append(output, kongConsumerGroups...) + output = append(output, []byte("---\n")...) + } + + return output, nil +} + +func (k KICContent) marshalKICContentToJson() ([]byte, error) { + + var kongIngresses []byte + var kongPlugins []byte + var kongClusterPlugins []byte + var ingresses []byte + var services []byte + var secrets []byte + var kongConsumers []byte + var kongConsumerGroups []byte + var err error + var output []byte + + // iterate over the slices of kongIngresses, kongPlugins, + // kongClusterPlugins, ingresses, services, secrets, kongConsumers + // and marshal each one in json format + // and append it to the output slice + // then return the output slice + for _, kongIngress := range k.KongIngresses { + kongIngresses, err = json.MarshalIndent(kongIngress, "", " ") + if err != nil { + return nil, err + } + output = append(output, kongIngresses...) + } + + for _, kongPlugin := range k.KongPlugins { + kongPlugins, err = json.MarshalIndent(kongPlugin, "", " ") + if err != nil { + return nil, err + } + output = append(output, kongPlugins...) + } + + for _, kongClusterPlugin := range k.KongClusterPlugins { + kongClusterPlugins, err = json.MarshalIndent(kongClusterPlugin, "", " ") + if err != nil { + return nil, err + } + output = append(output, kongClusterPlugins...) + } + + for _, ingress := range k.Ingresses { + ingresses, err = json.MarshalIndent(ingress, "", " ") + if err != nil { + return nil, err + } + output = append(output, ingresses...) + } + + for _, service := range k.Services { + services, err = json.MarshalIndent(service, "", " ") + if err != nil { + return nil, err + } + output = append(output, services...) + } + + for _, secret := range k.Secrets { + secrets, err = json.MarshalIndent(secret, "", " ") + if err != nil { + return nil, err + } + output = append(output, secrets...) + } + + for _, kongConsumer := range k.KongConsumers { + kongConsumers, err = json.MarshalIndent(kongConsumer, "", " ") + if err != nil { + return nil, err + } + output = append(output, kongConsumers...) + } + + for _, kongConsumerGroup := range k.KongConsumerGroups { + kongConsumerGroups, err = json.MarshalIndent(kongConsumerGroup, "", " ") + if err != nil { + return nil, err + } + output = append(output, kongConsumerGroups...) + } + + return output, nil +} + diff --git a/file/validate.go b/file/validate.go index 14076950c..ac2c3252b 100644 --- a/file/validate.go +++ b/file/validate.go @@ -1,6 +1,8 @@ package file import ( + "encoding/json" + "errors" "fmt" "github.com/kong/deck/utils" @@ -8,6 +10,15 @@ import ( "sigs.k8s.io/yaml" ) +type ValidationError struct { + Object string `json:"object"` + Err error `json:"error"` +} + +func (e *ValidationError) Error() string { + return fmt.Sprintf("validation error: object=%s, err=%v", e.Object, e.Err) +} + func validate(content []byte) error { var c map[string]interface{} err := yaml.Unmarshal(content, &c) @@ -24,10 +35,14 @@ func validate(content []byte) error { if result.Valid() { return nil } + var errs utils.ErrArray for _, desc := range result.Errors() { - err := fmt.Errorf(desc.String()) - errs.Errors = append(errs.Errors, err) + jsonString, err := json.Marshal(desc.Value()) + if err != nil { + return err + } + errs.Errors = append(errs.Errors, &ValidationError{Object: string(jsonString), Err: errors.New(desc.String())}) } return errs } @@ -41,3 +56,13 @@ func validateWorkspaces(workspaces []string) error { } return nil } + +func validateRuntimeGroups(names []string) error { + utils.RemoveDuplicates(&names) + if len(names) > 1 { + return fmt.Errorf("it seems like you are trying to sync multiple Konnect Runtime Groups "+ + "at the same time (%v).\ndecK doesn't support syncing multiple Runtime Groups at the same time, "+ + "please sync one Runtime Group at a time", names) + } + return nil +} diff --git a/file/writer.go b/file/writer.go index 4905f046c..4a08e5144 100644 --- a/file/writer.go +++ b/file/writer.go @@ -43,16 +43,16 @@ func getFormatVersion(kongVersion string) (string, error) { return formatVersion, nil } -// KongStateToFile writes a state object to file with filename. +// KongStateToFile generates a state object to file.Content. // It will omit timestamps and IDs while writing. -func KongStateToFile(kongState *state.KongState, config WriteConfig) error { +func KongStateToContent(kongState *state.KongState, config WriteConfig) (*Content, error) { file := &Content{} var err error file.Workspace = config.Workspace formatVersion, err := getFormatVersion(config.KongVersion) if err != nil { - return fmt.Errorf("get format version: %w", err) + return nil, fmt.Errorf("get format version: %w", err) } file.FormatVersion = formatVersion if config.RuntimeGroupName != "" { @@ -70,49 +70,58 @@ func KongStateToFile(kongState *state.KongState, config WriteConfig) error { err = populateServices(kongState, file, config) if err != nil { - return err + return nil, err } err = populateServicelessRoutes(kongState, file, config) if err != nil { - return err + return nil, err } err = populatePlugins(kongState, file, config) if err != nil { - return err + return nil, err } err = populateUpstreams(kongState, file, config) if err != nil { - return err + return nil, err } err = populateCertificates(kongState, file, config) if err != nil { - return err + return nil, err } err = populateCACertificates(kongState, file, config) if err != nil { - return err + return nil, err } err = populateConsumers(kongState, file, config) if err != nil { - return err + return nil, err } err = populateVaults(kongState, file, config) if err != nil { - return err + return nil, err } err = populateConsumerGroups(kongState, file, config) if err != nil { - return err + return nil, err } + return file, nil +} +// KongStateToFile writes a state object to file with filename. +// See KongStateToContent for the State generation +func KongStateToFile(kongState *state.KongState, config WriteConfig) error { + file, err := KongStateToContent(kongState, config) + if err != nil { + return err + } return WriteContentToFile(file, config.Filename, config.FileFormat) } @@ -382,7 +391,7 @@ func populatePlugins(kongState *state.KongState, file *Content, if p.Consumer != nil { associations++ cID := *p.Consumer.ID - consumer, err := kongState.Consumers.Get(cID) + consumer, err := kongState.Consumers.GetByIDOrUsername(cID) if err != nil { return fmt.Errorf("unable to get consumer %s for plugin %s [%s]: %w", cID, *p.Name, *p.ID, err) } @@ -415,6 +424,18 @@ func populatePlugins(kongState *state.KongState, file *Content, } p.Route.ID = &rID } + if p.ConsumerGroup != nil { + associations++ + cgID := *p.ConsumerGroup.ID + cg, err := kongState.ConsumerGroups.Get(cgID) + if err != nil { + return fmt.Errorf("unable to get consumer-group %s for plugin %s [%s]: %w", cgID, *p.Name, *p.ID, err) + } + if !utils.Empty(cg.Name) { + cgID = *cg.Name + } + p.ConsumerGroup.ID = &cgID + } if associations == 0 || associations > 1 { utils.ZeroOutID(p, p.Name, config.WithID) utils.ZeroOutTimestamps(p) @@ -703,13 +724,13 @@ func populateConsumerGroups(kongState *state.KongState, file *Content, if err != nil { return err } - plugins, err := kongState.ConsumerGroupPlugins.GetAll() + cgPlugins, err := kongState.ConsumerGroupPlugins.GetAll() if err != nil { return err } for _, cg := range consumerGroups { group := FConsumerGroupObject{ConsumerGroup: cg.ConsumerGroup} - for _, plugin := range plugins { + for _, plugin := range cgPlugins { if plugin.ID != nil && cg.ID != nil { if plugin.ConsumerGroup != nil && *plugin.ConsumerGroup.ID == *cg.ID { utils.ZeroOutID(plugin, plugin.Name, config.WithID) @@ -720,6 +741,25 @@ func populateConsumerGroups(kongState *state.KongState, file *Content, } } } + + plugins, err := kongState.Plugins.GetAllByConsumerGroupID(*cg.ID) + if err != nil { + return err + } + for _, plugin := range plugins { + if plugin.ID != nil && cg.ID != nil { + if plugin.ConsumerGroup != nil && *plugin.ConsumerGroup.ID == *cg.ID { + utils.ZeroOutID(plugin, plugin.Name, config.WithID) + utils.ZeroOutID(plugin.ConsumerGroup, plugin.ConsumerGroup.Name, config.WithID) + group.Plugins = append(group.Plugins, &kong.ConsumerGroupPlugin{ + ID: plugin.ID, + Name: plugin.Name, + Config: plugin.Config, + }) + } + } + } + utils.ZeroOutID(&group, group.Name, config.WithID) utils.ZeroOutTimestamps(&group) file.ConsumerGroups = append(file.ConsumerGroups, group) @@ -744,6 +784,26 @@ func WriteContentToFile(content *Content, filename string, format Format) error if err != nil { return err } + case KIC_JSON_CRD: + c, err = MarshalKongToKICJson(content, CUSTOM_RESOURCE) + if err != nil { + return err + } + case KIC_YAML_CRD: + c, err = MarshalKongToKICYaml(content, CUSTOM_RESOURCE) + if err != nil { + return err + } + case KIC_JSON_ANNOTATION: + c, err = MarshalKongToKICJson(content, ANNOTATIONS) + if err != nil { + return err + } + case KIC_YAML_ANNOTATION: + c, err = MarshalKongToKICYaml(content, ANNOTATIONS) + if err != nil { + return err + } default: return fmt.Errorf("unknown file format: " + string(format)) } diff --git a/file/writer_test.go b/file/writer_test.go index fea1cb0f7..12acc5931 100644 --- a/file/writer_test.go +++ b/file/writer_test.go @@ -402,4 +402,4 @@ func Test_getFormatVersion(t *testing.T) { } }) } -} +} \ No newline at end of file diff --git a/go.mod b/go.mod index 33178cc5d..a3e69f8d9 100644 --- a/go.mod +++ b/go.mod @@ -12,83 +12,113 @@ require ( github.com/fatih/color v1.15.0 github.com/google/go-cmp v0.5.9 github.com/google/go-querystring v1.1.0 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.3.1 github.com/hashicorp/go-memdb v1.3.4 - github.com/hashicorp/go-retryablehttp v0.7.2 + github.com/hashicorp/go-retryablehttp v0.7.4 github.com/hexops/gotextdiff v1.0.3 github.com/imdario/mergo v0.3.16 - github.com/kong/go-kong v0.44.0 + github.com/kong/go-apiops v0.1.20 + github.com/kong/go-kong v0.46.0 github.com/mitchellh/go-homedir v1.1.0 - github.com/shirou/gopsutil/v3 v3.23.4 + github.com/shirou/gopsutil/v3 v3.23.7 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 github.com/ssgelm/cookiejarparser v1.0.1 github.com/stretchr/testify v1.8.4 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/sync v0.2.0 - k8s.io/code-generator v0.27.3 + golang.org/x/sync v0.3.0 + k8s.io/code-generator v0.28.0 sigs.k8s.io/yaml v1.3.0 ) require ( github.com/Kong/go-diff v1.2.2 // indirect - github.com/adrg/strutil v0.2.3 // indirect + github.com/adrg/strutil v0.3.0 // indirect + github.com/cilium/ebpf v0.11.0 // indirect + github.com/cosiner/argv v0.1.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d // indirect + github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 // indirect + github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/getkin/kin-openapi v0.108.0 // indirect + github.com/go-delve/delve v1.21.0 // indirect + github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.1 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-dap v0.11.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/yaml v0.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kong/kubernetes-ingress-controller/v2 v2.11.1 // indirect github.com/kong/semver/v4 v4.0.1 // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mozillazg/go-slugify v0.2.0 // indirect + github.com/mozillazg/go-unidecode v0.2.0 // indirect + github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/shoenig/go-m1cpu v0.1.5 // indirect + github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/satori/go.uuid v1.2.0 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect github.com/tklauser/go-sysconf v0.3.11 // indirect - github.com/tklauser/numcpus v0.6.0 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect - github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect - golang.org/x/tools v0.7.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect + golang.org/x/tools v0.11.1 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.28.1 // indirect + k8s.io/apiextensions-apiserver v0.28.0 // indirect + k8s.io/apimachinery v0.28.1 // indirect k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect - k8s.io/klog/v2 v2.90.1 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/controller-runtime v0.15.1 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect ) diff --git a/go.sum b/go.sum index 8ca207b0a..9279e6c96 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,7 @@ cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= @@ -25,6 +26,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -35,68 +37,316 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= +contrib.go.opencensus.io/exporter/prometheus v0.2.1-0.20200609204449-6bcf6f8577f0/go.mod h1:MjHoxkI7Ny27toPeFkRbXbzVjzIGkwOAptrAy8Mxtm8= +contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= +contrib.go.opencensus.io/exporter/zipkin v0.1.2/go.mod h1:mP5xM3rrgOjpn79MM8fZbj3gsxcuytSqtH0dxSWW1RE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kong/go-diff v1.2.2 h1:KKKaqHc8IxuguFVIZMNt3bi6YuC/t9r7BGD8bOOpSgM= github.com/Kong/go-diff v1.2.2/go.mod h1:nlvdwVZQk3Rm+tbI0cDmKFrOjghtcZTrZBp+UruvvA8= +github.com/Kong/gojsondiff v1.3.0/go.mod h1:dbd2hU4/ecVIVMCKypBnQ5/ol5ALWb1f+XNJFE25j+c= github.com/Kong/gojsondiff v1.3.2 h1:qIOVq2mUXt+NXy8Be5gRUee9TP3Ve0MbQSafg9bXKZE= github.com/Kong/gojsondiff v1.3.2/go.mod h1:DiIxtU59q4alK7ecP+7k56C5UjgOviJ5gQVR2esEhYw= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/adrg/strutil v0.2.3 h1:WZVn3ItPBovFmP4wMHHVXUr8luRaHrbyIuLlHt32GZQ= github.com/adrg/strutil v0.2.3/go.mod h1:+SNxbiH6t+O+5SZqIj5n/9i5yUjR+S3XXVrjEcN2mxg= +github.com/adrg/strutil v0.3.0 h1:bi/HB2zQbDihC8lxvATDTDzkT4bG7PATtVnDYp5rvq4= +github.com/adrg/strutil v0.3.0/go.mod h1:Jz0wzBVE6Uiy9wxo62YEqEY1Nwto3QlLl1Il5gkLKWU= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/alecthomas/jsonschema v0.0.0-20180308105923-f2c93856175a/go.mod h1:qpebaTNSsyUn5rPSJMsfqEtDw71TTggXM6stUDI16HA= github.com/alecthomas/jsonschema v0.0.0-20191017121752-4bb6e3fae4f2 h1:swGeCLPiUQ647AIRnFxnAHdzlg6IPpmU6QdkOPZINt8= github.com/alecthomas/jsonschema v0.0.0-20191017121752-4bb6e3fae4f2/go.mod h1:Juc2PrI3wtNfUwptSvAIeNx+HrETwHQs6nf+TkOJlOA= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.31.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= +github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q= +github.com/bombsimon/logrusr v1.1.0/go.mod h1:Jq0nHtvxabKE5EMwAAdgTaz7dfWE8C4i11NOltxGQpc= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= +github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg= +github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/derekparker/trie v0.0.0-20221213183930-4c74548207f4/go.mod h1:C7Es+DLenIpPc9J6IYw4jrK0h7S9bKj4DNl8+KxGEXU= +github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d h1:hUWoLdw5kvo2xCsqlsIBMvWUc1QCSsCYD2J2+Fg6YoU= +github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d/go.mod h1:C7Es+DLenIpPc9J6IYw4jrK0h7S9bKj4DNl8+KxGEXU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-gk v0.0.0-20140819190930-201884a44051/go.mod h1:qm+vckxRlDt0aOla0RYJJVeqHZlWfOm2UIxHaqPB46E= +github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654/go.mod h1:qm+vckxRlDt0aOla0RYJJVeqHZlWfOm2UIxHaqPB46E= +github.com/dgryski/go-lttb v0.0.0-20180810165845-318fcdf10a77/go.mod h1:Va5MyIzkU0rAM92tn3hb3Anb7oz7KcnixF49+2wOMe4= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 h1:aRd8M7HJVZOqn/vhOzrGcQH0lNAMkqMn+pXUYkatmcA= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.11.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/getkin/kin-openapi v0.108.0 h1:EYf0GtsKa4hQNIlplGS+Au7NEfGQ1F7MoHD2kcVevPQ= +github.com/getkin/kin-openapi v0.108.0/go.mod h1:QtwUNt0PAAgIIBEvFWYfB7dfngxtAaqCX1zYHMZDeK8= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-delve/delve v1.21.0 h1:npcc8TZhdVxaMSJon+zqcE3bXM/ck8SSOOWw/id13jI= +github.com/go-delve/delve v1.21.0/go.mod h1:U+OAdfhewudkHsVs/AwhfpSBu7t/NgIXH3+my4T5q78= +github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d h1:pxjSLshkZJGLVm0wv20f/H0oTWiq/egkoJQ2ja6LEvo= +github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d/go.mod h1:biJCRbqp51wS+I92HMqn5H8/A0PAhxn2vyOT+JqhiGI= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= +github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v2.20.0+incompatible h1:4Xh3bDzO29j4TWNOI+24ubc0vbVFMg2PMnXKxK54/CA= +github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -124,10 +374,22 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= +github.com/gonum/diff v0.0.0-20181124234638-500114f11e71/go.mod h1:22dM4PLscQl+Nzf64qNBurVJvfyvZELT0iRW2l/NN70= +github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= +github.com/gonum/integrate v0.0.0-20181209220457-a422b5c0fdf2/go.mod h1:pDgmNM6seYpwvPos3q+zxlXMsbve6mOIPucUnUOrI7Y= +github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= +github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= +github.com/gonum/mathext v0.0.0-20181121095525-8a4bf007ea55/go.mod h1:fmo8aiSEWkJeiGXUJf+sPvuDgEFgqIoZSs843ePKrGg= +github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= +github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -136,16 +398,25 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-dap v0.9.1/go.mod h1:HAeyoSd2WIfTfg+0GRXcFrb+RnojAtGNh+k+XTIxJDE= +github.com/google/go-dap v0.11.0 h1:SpAZJL41rOOvd85PuLCCLE1dteTQOyKNnn0H3DBHywo= +github.com/google/go-dap v0.11.0/go.mod h1:HAeyoSd2WIfTfg+0GRXcFrb+RnojAtGNh+k+XTIxJDE= +github.com/google/go-github/v27 v27.0.6/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/mako v0.0.0-20190821191249-122f8dcef9e3/go.mod h1:YzLcVlL+NqWnmUEPuhS1LxDDwGO9WNbVlEXaF4IH35g= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -161,117 +432,412 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= +github.com/grpc-ecosystem/grpc-gateway v1.14.8/go.mod h1:NZE8t6vs6TnwLL/ITkaK8W3ecMLGAbh2jXTclvpiwYo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.2/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c= github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= -github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= -github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= +github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/tdigest v0.0.0-20180711151920-a7d76c6f093a/go.mod h1:9GkyshztGufsdPQWjH+ifgnIr3xNUL5syI70g2dzU1o= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= +github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kong/go-kong v0.44.0 h1:1x3w/TYdJjIZ6c1j9HiYP8755c923XN2O6j3kEaUkTA= -github.com/kong/go-kong v0.44.0/go.mod h1:41Sot1N/n8UHBp+gE/6nOw3vuzoHbhMSyU/zOS7VzPE= +github.com/kong/go-apiops v0.1.20 h1:MesWbep8Jvnk1FlbaFWgv4IkSi+DNojlSVmFt8INzrc= +github.com/kong/go-apiops v0.1.20/go.mod h1:3P9DBGLcU6Gp4wo8z4xohcg8PMutBAknc54pLZoQtDs= +github.com/kong/go-kong v0.46.0 h1:9I6nlX63WymU5Sg+d13iZDVwpW5vXh8/v0zarU27dzI= +github.com/kong/go-kong v0.46.0/go.mod h1:41Sot1N/n8UHBp+gE/6nOw3vuzoHbhMSyU/zOS7VzPE= +github.com/kong/kubernetes-ingress-controller/v2 v2.11.1 h1:ICadZZUXTEUE1O9ATVvB8ItNdHEQkF/SANv/CnLUbTw= +github.com/kong/kubernetes-ingress-controller/v2 v2.11.1/go.mod h1:BA6BvTy6b45Hw4nK5Oj6b29X8YjOPB8krhTgPr+hEQI= github.com/kong/semver/v4 v4.0.1 h1:DIcNR8W3gfx0KabFBADPalxxsp+q/5COwIFkkhrFQ2Y= github.com/kong/semver/v4 v4.0.1/go.mod h1:LImQ0oT15pJvSns/hs2laLca2zcYoHu5EsSNY0J6/QA= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.1-0.20191009090205-6c0755d89d1e/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.17/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/48M= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/onsi/ginkgo v1.1.1-0.20150303023352-38caab951a9f h1:znMoXRti03ZIy/gaW5cl3EO2A5zqzHBKA6uMnrAQDE0= -github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= -github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mozillazg/go-slugify v0.2.0 h1:SIhqDlnJWZH8OdiTmQgeXR28AOnypmAXPeOTcG7b9lk= +github.com/mozillazg/go-slugify v0.2.0/go.mod h1:z7dPH74PZf2ZPFkyxx+zjPD8CNzRJNa1CGacv0gg8Ns= +github.com/mozillazg/go-unidecode v0.2.0 h1:vFGEzAH9KSwyWmXCOblazEWDh7fOkpmy/Z4ArmamSUc= +github.com/mozillazg/go-unidecode v0.2.0/go.mod h1:zB48+/Z5toiRolOZy9ksLryJ976VIwmDmpQ2quyt1aA= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.1.1-0.20150303023352-38caab951a9f/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2 h1:uqH7bpe+ERSiDa34FDOF7RikN6RzXgduUF8yarlZp94= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= +github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= +github.com/onsi/gomega v0.0.0-20150401040250-4dfabf7db2e4/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.25.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rs/dnscache v0.0.0-20210201191234-295bba877686/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o= -github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8= -github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ= -github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ= -github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c= -github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= +github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/ssgelm/cookiejarparser v1.0.1 h1:cRdXauUbOTFzTPJFaeiWbHnQ+tRGlpKKzvIK9PUekE4= github.com/ssgelm/cookiejarparser v1.0.1/go.mod h1:DUfC0mpjIzlDN7DzKjXpHj0qMI5m9VrZuz3wSlI+OEI= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -279,26 +845,48 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= +github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tsenart/go-tsz v0.0.0-20180814232043-cdeb9e1e981e/go.mod h1:SWZznP1z5Ki7hDT2ioqiFKEse8K9tU2OUvaRI0NeGQo= +github.com/tsenart/vegeta/v12 v12.8.4/go.mod h1:ZiJtwLn/9M4fTPdMY7bdbIeyNeFVE8/AHbWFqCsUuho= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli v1.19.2-0.20170215051705-2526b57c56f3/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -306,29 +894,76 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yudai/golcs v0.0.0-20150405163532-d1c525dea8ce/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yudai/pp v2.0.2-0.20150410014804-be8315415630+incompatible h1:TmF93o7P81230DTx1l2zw5rZbsDpOOQXoKVCa8+nXXI= +github.com/yudai/pp v2.0.2-0.20150410014804-be8315415630+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4-0.20200608061201-1901b56b9515/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.starlark.net v0.0.0-20220816155156-cfacd8902214/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk= +go.starlark.net v0.0.0-20230831151029-c9e9adf3fde2 h1:JVsWByUy+MOggHrZ2zLfLUBOcinE2w18iSbLb7WAAIc= +go.starlark.net v0.0.0-20230831151029-c9e9adf3fde2/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= +golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= +golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -339,6 +974,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -360,22 +997,42 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -387,18 +1044,28 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -418,23 +1085,45 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -444,54 +1133,99 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -499,6 +1233,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -514,7 +1249,9 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -522,19 +1259,34 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc= +golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= @@ -544,6 +1296,7 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= @@ -551,9 +1304,11 @@ google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -562,6 +1317,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -580,8 +1336,10 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -589,15 +1347,22 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -607,9 +1372,12 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -622,25 +1390,59 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/evanphx/json-patch.v4 v4.9.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/pool.v3 v3.1.1/go.mod h1:pUAGBximS/hccTTSzEop6wvvQhVa3QPDFFW+8REdutg= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -648,21 +1450,74 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/code-generator v0.27.3 h1:JRhRQkzKdQhHmv9s5f7vuqveL8qukAQ2IqaHm6MFspM= -k8s.io/code-generator v0.27.3/go.mod h1:DPung1sI5vBgn4AGKtlPRQAyagj/ir/4jI55ipZHVww= +k8s.io/api v0.28.0/go.mod h1:0l8NZJzB0i/etuWnIXcwfIv+xnDOhL3lLW919AWYDuY= +k8s.io/api v0.28.1 h1:i+0O8k2NPBCPYaMB+uCkseEbawEt/eFaiRqUx8aB108= +k8s.io/api v0.28.1/go.mod h1:uBYwID+66wiL28Kn2tBjBYQdEU0Xk0z5qF8bIBqk/Dg= +k8s.io/apiextensions-apiserver v0.28.0 h1:CszgmBL8CizEnj4sj7/PtLGey6Na3YgWyGCPONv7E9E= +k8s.io/apiextensions-apiserver v0.28.0/go.mod h1:uRdYiwIuu0SyqJKriKmqEN2jThIJPhVmOWETm8ud1VE= +k8s.io/apimachinery v0.28.0 h1:ScHS2AG16UlYWk63r46oU3D5y54T53cVI5mMJwwqFNA= +k8s.io/apimachinery v0.28.0/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= +k8s.io/apimachinery v0.28.1 h1:EJD40og3GizBSV3mkIoXQBsws32okPOy+MkRyzh6nPY= +k8s.io/apimachinery v0.28.1/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= +k8s.io/code-generator v0.27.4 h1:bw2xFEBnthhCSC7Bt6FFHhPTfWX21IJ30GXxOzywsFE= +k8s.io/code-generator v0.27.4/go.mod h1:DPung1sI5vBgn4AGKtlPRQAyagj/ir/4jI55ipZHVww= +k8s.io/code-generator v0.28.0 h1:msdkRVJNVFgdiIJ8REl/d3cZsMB9HByFcWMmn13NyuE= +k8s.io/code-generator v0.28.0/go.mod h1:ueeSJZJ61NHBa0ccWLey6mwawum25vX61nRZ6WOzN9A= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d h1:U9tB195lKdzwqicbJvyJeOXV7Klv+wNAWENRnXEGi08= k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +knative.dev/hack v0.0.0-20210203173706-8368e1f6eacf/go.mod h1:PHt8x8yX5Z9pPquBEfIj0X66f8iWkWfR0S/sarACJrI= +knative.dev/networking v0.0.0-20210216014426-94bfc013982b/go.mod h1:Crdn87hxdFd3Jj6PIyrjzGnr8OGHX35k5xo9jlOrjjA= +knative.dev/pkg v0.0.0-20210215165523-84c98f3c3e7a/go.mod h1:TJSdebQOWX5N2bszohOYVi0H1QtXbtlYLuMghAFBMhY= +knative.dev/pkg v0.0.0-20210216013737-584933f8280b/go.mod h1:TJSdebQOWX5N2bszohOYVi0H1QtXbtlYLuMghAFBMhY= +pgregory.net/rapid v0.3.3/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/controller-runtime v0.8.3/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU= +sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= +sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= +sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c= +sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/state/builder.go b/state/builder.go index cf7e1c20a..97526dada 100644 --- a/state/builder.go +++ b/state/builder.go @@ -46,7 +46,7 @@ func ensureRoute(kongState *KongState, routeID string) (bool, *kong.Route, error } func ensureConsumer(kongState *KongState, consumerID string) (bool, *kong.Consumer, error) { - c, err := kongState.Consumers.Get(consumerID) + c, err := kongState.Consumers.GetByIDOrUsername(consumerID) if err != nil { if errors.Is(err, ErrNotFound) { return false, nil, nil @@ -57,6 +57,18 @@ func ensureConsumer(kongState *KongState, consumerID string) (bool, *kong.Consum return true, utils.GetConsumerReference(c.Consumer), nil } +func ensureConsumerGroup(kongState *KongState, consumerGroupID string) (bool, *kong.ConsumerGroup, error) { + c, err := kongState.ConsumerGroups.Get(consumerGroupID) + if err != nil { + if errors.Is(err, ErrNotFound) { + return false, nil, nil + } + return false, nil, fmt.Errorf("looking up consumer-group %q: %w", consumerGroupID, err) + + } + return true, utils.GetConsumerGroupReference(c.ConsumerGroup), nil +} + func buildKong(kongState *KongState, raw *utils.KongRawState) error { for _, s := range raw.Services { err := kongState.Services.Add(Service{Service: *s}) @@ -282,6 +294,15 @@ func buildKong(kongState *KongState, raw *utils.KongRawState) error { p.Consumer = c } } + if p.ConsumerGroup != nil && !utils.Empty(p.ConsumerGroup.ID) { + ok, cg, err := ensureConsumerGroup(kongState, *p.ConsumerGroup.ID) + if err != nil { + return err + } + if ok { + p.ConsumerGroup = cg + } + } err := kongState.Plugins.Add(Plugin{Plugin: *p}) if err != nil { return fmt.Errorf("inserting plugins into state: %w", err) diff --git a/state/consumer.go b/state/consumer.go index d2542c8ee..605311781 100644 --- a/state/consumer.go +++ b/state/consumer.go @@ -54,10 +54,23 @@ func (k *ConsumersCollection) Add(consumer Consumer) error { if !utils.Empty(consumer.Username) { searchBy = append(searchBy, *consumer.Username) } + + // search separately by id+username and by custom_id. + // + // This is because the custom_id is unique, but it may be equal to + // the username of another consumer. If we search by both id+username and + // custom_id, we may get a false positive. + _, err := getConsumer(txn, []string{"Username", "id"}, searchBy...) + if err == nil { + return fmt.Errorf("inserting consumer %v: %w", consumer.Console(), ErrAlreadyExists) + } else if !errors.Is(err, ErrNotFound) { + return err + } + if !utils.Empty(consumer.CustomID) { - searchBy = append(searchBy, *consumer.CustomID) + searchBy = []string{*consumer.CustomID} } - _, err := getConsumer(txn, searchBy...) + _, err = getConsumer(txn, []string{"CustomID"}, searchBy...) if err == nil { return fmt.Errorf("inserting consumer %v: %w", consumer.Console(), ErrAlreadyExists) } else if !errors.Is(err, ErrNotFound) { @@ -72,10 +85,9 @@ func (k *ConsumersCollection) Add(consumer Consumer) error { return nil } -func getConsumer(txn *memdb.Txn, IDs ...string) (*Consumer, error) { +func getConsumer(txn *memdb.Txn, indexes []string, IDs ...string) (*Consumer, error) { for _, id := range IDs { - res, err := multiIndexLookupUsingTxn(txn, consumerTableName, - []string{"Username", "id", "CustomID"}, id) + res, err := multiIndexLookupUsingTxn(txn, consumerTableName, indexes, id) if errors.Is(err, ErrNotFound) { continue } @@ -91,15 +103,26 @@ func getConsumer(txn *memdb.Txn, IDs ...string) (*Consumer, error) { return nil, ErrNotFound } -// Get gets a consumer by name or ID. -func (k *ConsumersCollection) Get(userNameOrID string) (*Consumer, error) { +// GetByIDOrUsername gets a consumer by name or ID. +func (k *ConsumersCollection) GetByIDOrUsername(userNameOrID string) (*Consumer, error) { if userNameOrID == "" { return nil, errIDRequired } txn := k.db.Txn(false) defer txn.Abort() - return getConsumer(txn, userNameOrID) + return getConsumer(txn, []string{"Username", "id"}, userNameOrID) +} + +// GetByCustomID gets a consumer by customID. +func (k *ConsumersCollection) GetByCustomID(customID string) (*Consumer, error) { + if customID == "" { + return nil, errIDRequired + } + + txn := k.db.Txn(false) + defer txn.Abort() + return getConsumer(txn, []string{"CustomID"}, customID) } // Update udpates an existing consumer. @@ -128,7 +151,7 @@ func (k *ConsumersCollection) Update(consumer Consumer) error { } func deleteConsumer(txn *memdb.Txn, userNameOrID string) error { - consumer, err := getConsumer(txn, userNameOrID) + consumer, err := getConsumer(txn, []string{"Username", "id"}, userNameOrID) if err != nil { return err } diff --git a/state/consumer_test.go b/state/consumer_test.go index 60922943e..35ecf5356 100644 --- a/state/consumer_test.go +++ b/state/consumer_test.go @@ -37,11 +37,11 @@ func TestConsumerGetUpdate(t *testing.T) { err := collection.Add(consumer) assert.Nil(err) - c, err := collection.Get("") + c, err := collection.GetByIDOrUsername("") assert.NotNil(err) assert.Nil(c) - c, err = collection.Get("first") + c, err = collection.GetByIDOrUsername("first") assert.Nil(err) assert.NotNil(c) @@ -56,11 +56,11 @@ func TestConsumerGetUpdate(t *testing.T) { c.ID = kong.String("first") assert.Nil(collection.Update(*c)) - c, err = collection.Get("my-name") + c, err = collection.GetByIDOrUsername("my-name") assert.NotNil(err) assert.Nil(c) - c, err = collection.Get("my-updated-name") + c, err = collection.GetByIDOrUsername("my-updated-name") assert.Nil(err) assert.NotNil(c) } @@ -77,12 +77,12 @@ func TestConsumerGetMemoryReference(t *testing.T) { err := collection.Add(consumer) assert.Nil(err) - c, err := collection.Get("first") + c, err := collection.GetByIDOrUsername("first") assert.Nil(err) assert.NotNil(c) c.Username = kong.String("update-should-not-reflect") - c, err = collection.Get("first") + c, err = collection.GetByIDOrUsername("first") assert.Nil(err) assert.Equal("my-name", *c.Username) } @@ -100,7 +100,7 @@ func TestConsumersInvalidType(t *testing.T) { txn.Commit() assert.Panics(func() { - collection.Get("my-name") + collection.GetByIDOrUsername("my-name") }) assert.Panics(func() { collection.GetAll() @@ -117,7 +117,7 @@ func TestConsumerDelete(t *testing.T) { err := collection.Add(consumer) assert.Nil(err) - c, err := collection.Get("my-consumer") + c, err := collection.GetByIDOrUsername("my-consumer") assert.Nil(err) assert.NotNil(c) assert.Equal("first", *c.ID) diff --git a/state/plugin.go b/state/plugin.go index 841ac1d6f..0b3951990 100644 --- a/state/plugin.go +++ b/state/plugin.go @@ -12,10 +12,11 @@ import ( var errPluginNameRequired = fmt.Errorf("name of plugin required") const ( - pluginTableName = "plugin" - pluginsByServiceID = "pluginsByServiceID" - pluginsByRouteID = "pluginsByRouteID" - pluginsByConsumerID = "pluginsByConsumerID" + pluginTableName = "plugin" + pluginsByServiceID = "pluginsByServiceID" + pluginsByRouteID = "pluginsByRouteID" + pluginsByConsumerID = "pluginsByConsumerID" + pluginsByConsumerGroupID = "pluginsByConsumerGroupID" ) var pluginTableSchema = &memdb.TableSchema{ @@ -68,6 +69,18 @@ var pluginTableSchema = &memdb.TableSchema{ }, AllowMissing: true, }, + pluginsByConsumerGroupID: { + Name: pluginsByConsumerGroupID, + Indexer: &indexers.SubFieldIndexer{ + Fields: []indexers.Field{ + { + Struct: "ConsumerGroup", + Sub: "ID", + }, + }, + }, + AllowMissing: true, + }, // combined foreign fields // FIXME bug: collision if svc/route/consumer has the same ID // and same type of plugin is created. Consider the case when only @@ -92,6 +105,10 @@ var pluginTableSchema = &memdb.TableSchema{ Struct: "Consumer", Sub: "ID", }, + { + Struct: "ConsumerGroup", + Sub: "ID", + }, }, }, }, @@ -133,7 +150,7 @@ func insertPlugin(txn *memdb.Txn, plugin Plugin) error { } // err out if another plugin with exact same combination is present - sID, rID, cID := "", "", "" + sID, rID, cID, cgID := "", "", "", "" if plugin.Service != nil && !utils.Empty(plugin.Service.ID) { sID = *plugin.Service.ID } @@ -143,7 +160,10 @@ func insertPlugin(txn *memdb.Txn, plugin Plugin) error { if plugin.Consumer != nil && !utils.Empty(plugin.Consumer.ID) { cID = *plugin.Consumer.ID } - _, err = getPluginBy(txn, *plugin.Name, sID, rID, cID) + if plugin.ConsumerGroup != nil && !utils.Empty(plugin.ConsumerGroup.ID) { + cgID = *plugin.ConsumerGroup.ID + } + _, err = getPluginBy(txn, *plugin.Name, sID, rID, cID, cgID) if err == nil { return fmt.Errorf("inserting plugin %v: %w", plugin.Console(), ErrAlreadyExists) } else if !errors.Is(err, ErrNotFound) { @@ -194,7 +214,7 @@ func (k *PluginsCollection) GetAllByName(name string) ([]*Plugin, error) { return k.getAllPluginsBy("name", name) } -func getPluginBy(txn *memdb.Txn, name, svcID, routeID, consumerID string) ( +func getPluginBy(txn *memdb.Txn, name, svcID, routeID, consumerID, consumerGroupID string) ( *Plugin, error, ) { if name == "" { @@ -202,7 +222,7 @@ func getPluginBy(txn *memdb.Txn, name, svcID, routeID, consumerID string) ( } res, err := txn.First(pluginTableName, "fields", - name, svcID, routeID, consumerID) + name, svcID, routeID, consumerID, consumerGroupID) if err != nil { return nil, err } @@ -217,18 +237,18 @@ func getPluginBy(txn *memdb.Txn, name, svcID, routeID, consumerID string) ( } // GetByProp returns a plugin which matches all the properties passed in -// the arguments. If serviceID, routeID and consumerID are empty strings, then -// a global plugin is searched. +// the arguments. If serviceID, routeID, consumerID and consumerGroupID +// are empty strings, then a global plugin is searched. // Otherwise, a plugin with name and the supplied foreign references is // searched. // name is required. -func (k *PluginsCollection) GetByProp(name, serviceID, - routeID string, consumerID string, +func (k *PluginsCollection) GetByProp( + name, serviceID, routeID, consumerID, consumerGroupID string, ) (*Plugin, error) { txn := k.db.Txn(false) defer txn.Abort() - return getPluginBy(txn, name, serviceID, routeID, consumerID) + return getPluginBy(txn, name, serviceID, routeID, consumerID, consumerGroupID) } func (k *PluginsCollection) getAllPluginsBy(index, identifier string) ( @@ -264,7 +284,7 @@ func (k *PluginsCollection) GetAllByServiceID(id string) ([]*Plugin, return k.getAllPluginsBy(pluginsByServiceID, id) } -// GetAllByRouteID returns all plugins referencing a service +// GetAllByRouteID returns all plugins referencing a route // by its id. func (k *PluginsCollection) GetAllByRouteID(id string) ([]*Plugin, error, @@ -280,6 +300,14 @@ func (k *PluginsCollection) GetAllByConsumerID(id string) ([]*Plugin, return k.getAllPluginsBy(pluginsByConsumerID, id) } +// GetAllByConsumerGroupID returns all plugins referencing a consumer-group +// by its id. +func (k *PluginsCollection) GetAllByConsumerGroupID(id string) ([]*Plugin, + error, +) { + return k.getAllPluginsBy(pluginsByConsumerGroupID, id) +} + // Update updates a plugin func (k *PluginsCollection) Update(plugin Plugin) error { // TODO abstract this check in the go-memdb library itself diff --git a/state/plugin_test.go b/state/plugin_test.go index 5ba22171b..f808806c8 100644 --- a/state/plugin_test.go +++ b/state/plugin_test.go @@ -271,9 +271,25 @@ func TestPluginsCollection_Update(t *testing.T) { }, }, } + plugin4 := Plugin{ + Plugin: kong.Plugin{ + ID: kong.String("id4"), + Name: kong.String("key-auth"), + Route: &kong.Route{ + ID: kong.String("route1"), + }, + Service: &kong.Service{ + ID: kong.String("svc1"), + }, + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("cg1"), + }, + }, + } k.Add(plugin1) k.Add(plugin2) k.Add(plugin3) + k.Add(plugin4) for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { @@ -362,6 +378,18 @@ func TestGetPluginByProp(t *testing.T) { }, }, }, + { + Plugin: kong.Plugin{ + ID: kong.String("5"), + Name: kong.String("key-auth"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("cg1"), + }, + Config: map[string]interface{}{ + "key5": "value5", + }, + }, + }, } assert := assert.New(t) collection := pluginsCollection() @@ -370,33 +398,38 @@ func TestGetPluginByProp(t *testing.T) { assert.Nil(collection.Add(p)) } - plugin, err := collection.GetByProp("", "", "", "") + plugin, err := collection.GetByProp("", "", "", "", "") assert.Nil(plugin) - assert.NotNil(err) + assert.Error(err) - plugin, err = collection.GetByProp("foo", "", "", "") + plugin, err = collection.GetByProp("foo", "", "", "", "") assert.Nil(plugin) assert.Equal(ErrNotFound, err) - plugin, err = collection.GetByProp("key-auth", "", "", "") - assert.Nil(err) + plugin, err = collection.GetByProp("key-auth", "", "", "", "") + assert.NoError(err) assert.NotNil(plugin) assert.Equal("value1", plugin.Config["key1"]) - plugin, err = collection.GetByProp("key-auth", "svc1", "", "") - assert.Nil(err) + plugin, err = collection.GetByProp("key-auth", "svc1", "", "", "") + assert.NoError(err) assert.NotNil(plugin) assert.Equal("value2", plugin.Config["key2"]) - plugin, err = collection.GetByProp("key-auth", "", "route1", "") - assert.Nil(err) + plugin, err = collection.GetByProp("key-auth", "", "route1", "", "") + assert.NoError(err) assert.NotNil(plugin) assert.Equal("value3", plugin.Config["key3"]) - plugin, err = collection.GetByProp("key-auth", "", "", "consumer1") - assert.Nil(err) + plugin, err = collection.GetByProp("key-auth", "", "", "consumer1", "") + assert.NoError(err) assert.NotNil(plugin) assert.Equal("value4", plugin.Config["key4"]) + + plugin, err = collection.GetByProp("key-auth", "", "", "", "cg1") + assert.NoError(err) + assert.NotNil(plugin) + assert.Equal("value5", plugin.Config["key5"]) } func TestPluginsInvalidType(t *testing.T) { diff --git a/state/types.go b/state/types.go index 88dcb5795..b9f14fb3c 100644 --- a/state/types.go +++ b/state/types.go @@ -98,6 +98,13 @@ func (s1 *Service) EqualWithOpts(s2 *Service, s1Copy := s1.Service.DeepCopy() s2Copy := s2.Service.DeepCopy() + if len(s1Copy.Tags) == 0 { + s1Copy.Tags = nil + } + if len(s2Copy.Tags) == 0 { + s2Copy.Tags = nil + } + // Cassandra can sometimes mess up tag order, but tag order doesn't actually matter: tags are sets // even though we represent them with slices. Sort before comparison to avoid spurious diff detection. sort.Slice(s1Copy.Tags, func(i, j int) bool { return *(s1Copy.Tags[i]) < *(s1Copy.Tags[j]) }) @@ -153,6 +160,13 @@ func (r1 *Route) EqualWithOpts(r2 *Route, ignoreID, r1Copy := r1.Route.DeepCopy() r2Copy := r2.Route.DeepCopy() + if len(r1Copy.Tags) == 0 { + r1Copy.Tags = nil + } + if len(r2Copy.Tags) == 0 { + r2Copy.Tags = nil + } + sort.Slice(r1Copy.Tags, func(i, j int) bool { return *(r1Copy.Tags[i]) < *(r1Copy.Tags[j]) }) sort.Slice(r2Copy.Tags, func(i, j int) bool { return *(r2Copy.Tags[i]) < *(r2Copy.Tags[j]) }) @@ -216,6 +230,13 @@ func (u1 *Upstream) EqualWithOpts(u2 *Upstream, u1Copy := u1.Upstream.DeepCopy() u2Copy := u2.Upstream.DeepCopy() + if len(u1Copy.Tags) == 0 { + u1Copy.Tags = nil + } + if len(u2Copy.Tags) == 0 { + u2Copy.Tags = nil + } + sort.Slice(u1Copy.Tags, func(i, j int) bool { return *(u1Copy.Tags[i]) < *(u1Copy.Tags[j]) }) sort.Slice(u2Copy.Tags, func(i, j int) bool { return *(u2Copy.Tags[i]) < *(u2Copy.Tags[j]) }) @@ -270,6 +291,13 @@ func (t1 *Target) EqualWithOpts(t2 *Target, ignoreID, t1Copy := t1.Target.DeepCopy() t2Copy := t2.Target.DeepCopy() + if len(t1Copy.Tags) == 0 { + t1Copy.Tags = nil + } + if len(t2Copy.Tags) == 0 { + t2Copy.Tags = nil + } + sort.Slice(t1Copy.Tags, func(i, j int) bool { return *(t1Copy.Tags[i]) < *(t1Copy.Tags[j]) }) sort.Slice(t2Copy.Tags, func(i, j int) bool { return *(t2Copy.Tags[i]) < *(t2Copy.Tags[j]) }) @@ -324,6 +352,13 @@ func (c1 *Certificate) EqualWithOpts(c2 *Certificate, c1Copy := c1.Certificate.DeepCopy() c2Copy := c2.Certificate.DeepCopy() + if len(c1Copy.Tags) == 0 { + c1Copy.Tags = nil + } + if len(c2Copy.Tags) == 0 { + c2Copy.Tags = nil + } + sort.Slice(c1Copy.Tags, func(i, j int) bool { return *(c1Copy.Tags[i]) < *(c1Copy.Tags[j]) }) sort.Slice(c2Copy.Tags, func(i, j int) bool { return *(c2Copy.Tags[i]) < *(c2Copy.Tags[j]) }) @@ -374,6 +409,13 @@ func (s1 *SNI) EqualWithOpts(s2 *SNI, ignoreID, s1Copy := s1.SNI.DeepCopy() s2Copy := s2.SNI.DeepCopy() + if len(s1Copy.Tags) == 0 { + s1Copy.Tags = nil + } + if len(s2Copy.Tags) == 0 { + s2Copy.Tags = nil + } + sort.Slice(s1Copy.Tags, func(i, j int) bool { return *(s1Copy.Tags[i]) < *(s1Copy.Tags[j]) }) sort.Slice(s2Copy.Tags, func(i, j int) bool { return *(s2Copy.Tags[i]) < *(s2Copy.Tags[j]) }) @@ -425,6 +467,9 @@ func (p1 *Plugin) Console() string { if p1.Consumer != nil { associations = append(associations, "consumer "+p1.Consumer.FriendlyName()) } + if p1.ConsumerGroup != nil { + associations = append(associations, "consumer-group "+p1.ConsumerGroup.FriendlyName()) + } if len(associations) > 0 { res += "for " } @@ -452,6 +497,13 @@ func (p1 *Plugin) EqualWithOpts(p2 *Plugin, ignoreID, p1Copy := p1.Plugin.DeepCopy() p2Copy := p2.Plugin.DeepCopy() + if len(p1Copy.Tags) == 0 { + p1Copy.Tags = nil + } + if len(p2Copy.Tags) == 0 { + p2Copy.Tags = nil + } + sort.Slice(p1Copy.Tags, func(i, j int) bool { return *(p1Copy.Tags[i]) < *(p1Copy.Tags[j]) }) sort.Slice(p2Copy.Tags, func(i, j int) bool { return *(p2Copy.Tags[i]) < *(p2Copy.Tags[j]) }) @@ -470,6 +522,7 @@ func (p1 *Plugin) EqualWithOpts(p2 *Plugin, ignoreID, p2Copy.Service = nil p2Copy.Route = nil p2Copy.Consumer = nil + p2Copy.ConsumerGroup = nil } if p1Copy.Service != nil { @@ -490,6 +543,12 @@ func (p1 *Plugin) EqualWithOpts(p2 *Plugin, ignoreID, if p2Copy.Consumer != nil { p2Copy.Consumer.Username = nil } + if p1Copy.ConsumerGroup != nil { + p1Copy.ConsumerGroup.Name = nil + } + if p2Copy.ConsumerGroup != nil { + p2Copy.ConsumerGroup.Name = nil + } return reflect.DeepEqual(p1Copy, p2Copy) } @@ -528,6 +587,13 @@ func (c1 *Consumer) EqualWithOpts(c2 *Consumer, c1Copy := c1.Consumer.DeepCopy() c2Copy := c2.Consumer.DeepCopy() + if len(c1Copy.Tags) == 0 { + c1Copy.Tags = nil + } + if len(c2Copy.Tags) == 0 { + c2Copy.Tags = nil + } + sort.Slice(c1Copy.Tags, func(i, j int) bool { return *(c1Copy.Tags[i]) < *(c1Copy.Tags[j]) }) sort.Slice(c2Copy.Tags, func(i, j int) bool { return *(c2Copy.Tags[i]) < *(c2Copy.Tags[j]) }) @@ -778,6 +844,13 @@ func (k1 *KeyAuth) EqualWithOpts(k2 *KeyAuth, ignoreID, k1Copy := k1.KeyAuth.DeepCopy() k2Copy := k2.KeyAuth.DeepCopy() + if len(k1Copy.Tags) == 0 { + k1Copy.Tags = nil + } + if len(k2Copy.Tags) == 0 { + k2Copy.Tags = nil + } + sort.Slice(k1Copy.Tags, func(i, j int) bool { return *(k1Copy.Tags[i]) < *(k1Copy.Tags[j]) }) sort.Slice(k2Copy.Tags, func(i, j int) bool { return *(k2Copy.Tags[i]) < *(k2Copy.Tags[j]) }) @@ -856,6 +929,13 @@ func (h1 *HMACAuth) EqualWithOpts(h2 *HMACAuth, ignoreID, h1Copy := h1.HMACAuth.DeepCopy() h2Copy := h2.HMACAuth.DeepCopy() + if len(h1Copy.Tags) == 0 { + h1Copy.Tags = nil + } + if len(h2Copy.Tags) == 0 { + h2Copy.Tags = nil + } + sort.Slice(h1Copy.Tags, func(i, j int) bool { return *(h1Copy.Tags[i]) < *(h1Copy.Tags[j]) }) sort.Slice(h2Copy.Tags, func(i, j int) bool { return *(h2Copy.Tags[i]) < *(h2Copy.Tags[j]) }) @@ -934,6 +1014,13 @@ func (j1 *JWTAuth) EqualWithOpts(j2 *JWTAuth, ignoreID, j1Copy := j1.JWTAuth.DeepCopy() j2Copy := j2.JWTAuth.DeepCopy() + if len(j1Copy.Tags) == 0 { + j1Copy.Tags = nil + } + if len(j2Copy.Tags) == 0 { + j2Copy.Tags = nil + } + sort.Slice(j1Copy.Tags, func(i, j int) bool { return *(j1Copy.Tags[i]) < *(j1Copy.Tags[j]) }) sort.Slice(j2Copy.Tags, func(i, j int) bool { return *(j2Copy.Tags[i]) < *(j2Copy.Tags[j]) }) @@ -1012,6 +1099,13 @@ func (b1 *BasicAuth) EqualWithOpts(b2 *BasicAuth, ignoreID, b1Copy := b1.BasicAuth.DeepCopy() b2Copy := b2.BasicAuth.DeepCopy() + if len(b1Copy.Tags) == 0 { + b1Copy.Tags = nil + } + if len(b2Copy.Tags) == 0 { + b2Copy.Tags = nil + } + sort.Slice(b1Copy.Tags, func(i, j int) bool { return *(b1Copy.Tags[i]) < *(b1Copy.Tags[j]) }) sort.Slice(b2Copy.Tags, func(i, j int) bool { return *(b2Copy.Tags[i]) < *(b2Copy.Tags[j]) }) @@ -1094,6 +1188,13 @@ func (b1 *ACLGroup) EqualWithOpts(b2 *ACLGroup, ignoreID, b1Copy := b1.ACLGroup.DeepCopy() b2Copy := b2.ACLGroup.DeepCopy() + if len(b1Copy.Tags) == 0 { + b1Copy.Tags = nil + } + if len(b2Copy.Tags) == 0 { + b2Copy.Tags = nil + } + sort.Slice(b1Copy.Tags, func(i, j int) bool { return *(b1Copy.Tags[i]) < *(b1Copy.Tags[j]) }) sort.Slice(b2Copy.Tags, func(i, j int) bool { return *(b2Copy.Tags[i]) < *(b2Copy.Tags[j]) }) @@ -1154,6 +1255,13 @@ func (c1 *CACertificate) EqualWithOpts(c2 *CACertificate, c1Copy := c1.CACertificate.DeepCopy() c2Copy := c2.CACertificate.DeepCopy() + if len(c1Copy.Tags) == 0 { + c1Copy.Tags = nil + } + if len(c2Copy.Tags) == 0 { + c2Copy.Tags = nil + } + sort.Slice(c1Copy.Tags, func(i, j int) bool { return *(c1Copy.Tags[i]) < *(c1Copy.Tags[j]) }) sort.Slice(c2Copy.Tags, func(i, j int) bool { return *(c2Copy.Tags[i]) < *(c2Copy.Tags[j]) }) @@ -1195,6 +1303,13 @@ func (k1 *Oauth2Credential) EqualWithOpts(k2 *Oauth2Credential, ignoreID, k1Copy := k1.Oauth2Credential.DeepCopy() k2Copy := k2.Oauth2Credential.DeepCopy() + if len(k1Copy.Tags) == 0 { + k1Copy.Tags = nil + } + if len(k2Copy.Tags) == 0 { + k2Copy.Tags = nil + } + sort.Slice(k1Copy.Tags, func(i, j int) bool { return *(k1Copy.Tags[i]) < *(k1Copy.Tags[j]) }) sort.Slice(k2Copy.Tags, func(i, j int) bool { return *(k2Copy.Tags[i]) < *(k2Copy.Tags[j]) }) @@ -1273,6 +1388,13 @@ func (b1 *MTLSAuth) EqualWithOpts(b2 *MTLSAuth, ignoreID, b1Copy := b1.MTLSAuth.DeepCopy() b2Copy := b2.MTLSAuth.DeepCopy() + if len(b1Copy.Tags) == 0 { + b1Copy.Tags = nil + } + if len(b2Copy.Tags) == 0 { + b2Copy.Tags = nil + } + sort.Slice(b1Copy.Tags, func(i, j int) bool { return *(b1Copy.Tags[i]) < *(b1Copy.Tags[j]) }) sort.Slice(b2Copy.Tags, func(i, j int) bool { return *(b2Copy.Tags[i]) < *(b2Copy.Tags[j]) }) @@ -1454,6 +1576,13 @@ func (v1 *Vault) EqualWithOpts(v2 *Vault, ignoreID, ignoreTS bool) bool { v1Copy := v1.Vault.DeepCopy() v2Copy := v2.Vault.DeepCopy() + if len(v1Copy.Tags) == 0 { + v1Copy.Tags = nil + } + if len(v2Copy.Tags) == 0 { + v2Copy.Tags = nil + } + sort.Slice(v1Copy.Tags, func(i, j int) bool { return *(v1Copy.Tags[i]) < *(v1Copy.Tags[j]) }) sort.Slice(v2Copy.Tags, func(i, j int) bool { return *(v2Copy.Tags[i]) < *(v2Copy.Tags[j]) }) diff --git a/tests/integration/diff_test.go b/tests/integration/diff_test.go index 1f9f94cf9..b9d3a7184 100644 --- a/tests/integration/diff_test.go +++ b/tests/integration/diff_test.go @@ -67,6 +67,394 @@ Summary: "DECK_FUB": "fubfub", // unused "DECK_FOO": "foo_test", // unused, partial match } + + expectedOutputUnMaskedJSON = `{ + "changes": { + "creating": [ + { + "name": "rate-limiting (global)", + "kind": "plugin", + "body": { + "new": { + "id": "a1368a28-cb5c-4eee-86d8-03a6bdf94b5e", + "name": "rate-limiting", + "config": { + "day": null, + "error_code": 429, + "error_message": "API rate limit exceeded", + "fault_tolerant": true, + "header_name": null, + "hide_client_headers": false, + "hour": null, + "limit_by": "consumer", + "minute": 123, + "month": null, + "path": null, + "policy": "local", + "redis_database": 0, + "redis_host": null, + "redis_password": null, + "redis_port": 6379, + "redis_server_name": null, + "redis_ssl": false, + "redis_ssl_verify": false, + "redis_timeout": 2000, + "redis_username": null, + "second": null, + "year": null + }, + "enabled": true, + "protocols": [ + "grpc", + "grpcs", + "http", + "https" + ] + }, + "old": null + } + } + ], + "updating": [ + { + "name": "svc1", + "kind": "service", + "body": { + "new": { + "connect_timeout": 60000, + "enabled": true, + "host": "mockbin.org", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000, + "tags": [ + "test" + ] + }, + "old": { + "connect_timeout": 60000, + "enabled": true, + "host": "mockbin.org", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000 + } + } + } + ], + "deleting": [] + }, + "summary": { + "creating": 1, + "updating": 1, + "deleting": 0, + "total": 2 + }, + "warnings": [], + "errors": [] +} + +` + + expectedOutputMaskedJSON = `{ + "changes": { + "creating": [ + { + "name": "rate-limiting (global)", + "kind": "plugin", + "body": { + "new": { + "id": "a1368a28-cb5c-4eee-86d8-03a6bdf94b5e", + "name": "rate-limiting", + "config": { + "day": null, + "error_code": 429, + "error_message": "API rate limit exceeded", + "fault_tolerant": true, + "header_name": null, + "hide_client_headers": false, + "hour": null, + "limit_by": "consumer", + "minute": 123, + "month": null, + "path": null, + "policy": "local", + "redis_database": 0, + "redis_host": null, + "redis_password": null, + "redis_port": 6379, + "redis_server_name": null, + "redis_ssl": false, + "redis_ssl_verify": false, + "redis_timeout": 2000, + "redis_username": null, + "second": null, + "year": null + }, + "enabled": true, + "protocols": [ + "grpc", + "grpcs", + "http", + "https" + ] + }, + "old": null + } + } + ], + "updating": [ + { + "name": "svc1", + "kind": "service", + "body": { + "new": { + "connect_timeout": 60000, + "enabled": true, + "host": "[masked]", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000, + "tags": [ + "[masked] is an external host. I like [masked]!", + "foo:foo", + "baz:[masked]", + "another:[masked]", + "bar:[masked]" + ] + }, + "old": { + "connect_timeout": 60000, + "enabled": true, + "host": "[masked]", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000 + } + } + } + ], + "deleting": [] + }, + "summary": { + "creating": 1, + "updating": 1, + "deleting": 0, + "total": 2 + }, + "warnings": [], + "errors": [] +} + +` + + expectedOutputUnMaskedJSON30x = `{ + "changes": { + "creating": [ + { + "name": "rate-limiting (global)", + "kind": "plugin", + "body": { + "new": { + "id": "a1368a28-cb5c-4eee-86d8-03a6bdf94b5e", + "name": "rate-limiting", + "config": { + "day": null, + "fault_tolerant": true, + "header_name": null, + "hide_client_headers": false, + "hour": null, + "limit_by": "consumer", + "minute": 123, + "month": null, + "path": null, + "policy": "local", + "redis_database": 0, + "redis_host": null, + "redis_password": null, + "redis_port": 6379, + "redis_server_name": null, + "redis_ssl": false, + "redis_ssl_verify": false, + "redis_timeout": 2000, + "redis_username": null, + "second": null, + "year": null + }, + "enabled": true, + "protocols": [ + "grpc", + "grpcs", + "http", + "https" + ] + }, + "old": null + } + } + ], + "updating": [ + { + "name": "svc1", + "kind": "service", + "body": { + "new": { + "connect_timeout": 60000, + "enabled": true, + "host": "mockbin.org", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000, + "tags": [ + "test" + ] + }, + "old": { + "connect_timeout": 60000, + "enabled": true, + "host": "mockbin.org", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000 + } + } + } + ], + "deleting": [] + }, + "summary": { + "creating": 1, + "updating": 1, + "deleting": 0, + "total": 2 + }, + "warnings": [], + "errors": [] +} + +` + + expectedOutputMaskedJSON30x = `{ + "changes": { + "creating": [ + { + "name": "rate-limiting (global)", + "kind": "plugin", + "body": { + "new": { + "id": "a1368a28-cb5c-4eee-86d8-03a6bdf94b5e", + "name": "rate-limiting", + "config": { + "day": null, + "fault_tolerant": true, + "header_name": null, + "hide_client_headers": false, + "hour": null, + "limit_by": "consumer", + "minute": 123, + "month": null, + "path": null, + "policy": "local", + "redis_database": 0, + "redis_host": null, + "redis_password": null, + "redis_port": 6379, + "redis_server_name": null, + "redis_ssl": false, + "redis_ssl_verify": false, + "redis_timeout": 2000, + "redis_username": null, + "second": null, + "year": null + }, + "enabled": true, + "protocols": [ + "grpc", + "grpcs", + "http", + "https" + ] + }, + "old": null + } + } + ], + "updating": [ + { + "name": "svc1", + "kind": "service", + "body": { + "new": { + "connect_timeout": 60000, + "enabled": true, + "host": "[masked]", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000, + "tags": [ + "[masked] is an external host. I like [masked]!", + "foo:foo", + "baz:[masked]", + "another:[masked]", + "bar:[masked]" + ] + }, + "old": { + "connect_timeout": 60000, + "enabled": true, + "host": "[masked]", + "id": "9ecf5708-f2f4-444e-a4c7-fcd3a57f9a6d", + "name": "svc1", + "port": 80, + "protocol": "http", + "read_timeout": 60000, + "retries": 5, + "write_timeout": 60000 + } + } + } + ], + "deleting": [] + }, + "summary": { + "creating": 1, + "updating": 1, + "deleting": 0, + "total": 2 + }, + "warnings": [], + "errors": [] +} + +` ) // test scope: @@ -86,8 +474,7 @@ func Test_Diff_Workspace_OlderThan3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "<3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) _, err := diff(tc.stateFile) assert.NoError(t, err) @@ -111,8 +498,7 @@ func Test_Diff_Workspace_NewerThan3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) _, err := diff(tc.stateFile) assert.NoError(t, err) @@ -143,8 +529,7 @@ func Test_Diff_Masked_OlderThan3x(t *testing.T) { t.Setenv(k, v) } runWhen(t, "kong", "==2.8.0") - teardown := setup(t) - defer teardown(t) + setup(t) // initialize state assert.NoError(t, sync(tc.initialStateFile)) @@ -154,6 +539,23 @@ func Test_Diff_Masked_OlderThan3x(t *testing.T) { assert.Equal(t, expectedOutputMasked, out) }) } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", "==2.8.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputMaskedJSON, out) + }) + } } // test scope: @@ -179,8 +581,7 @@ func Test_Diff_Masked_NewerThan3x(t *testing.T) { t.Setenv(k, v) } runWhen(t, "kong", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // initialize state assert.NoError(t, sync(tc.initialStateFile)) @@ -190,11 +591,43 @@ func Test_Diff_Masked_NewerThan3x(t *testing.T) { assert.Equal(t, expectedOutputMasked, out) }) } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", ">=3.0.0 <3.1.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputMaskedJSON30x, out) + }) + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", ">=3.1.0 <3.4.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputMaskedJSON, out) + }) + } } // test scope: // - 2.8.0 -func Test_Diff_Unasked_OlderThan3x(t *testing.T) { +func Test_Diff_Unmasked_OlderThan3x(t *testing.T) { tests := []struct { name string initialStateFile string @@ -215,8 +648,7 @@ func Test_Diff_Unasked_OlderThan3x(t *testing.T) { t.Setenv(k, v) } runWhen(t, "kong", "==2.8.0") - teardown := setup(t) - defer teardown(t) + setup(t) // initialize state assert.NoError(t, sync(tc.initialStateFile)) @@ -226,11 +658,27 @@ func Test_Diff_Unasked_OlderThan3x(t *testing.T) { assert.Equal(t, expectedOutputUnMasked, out) }) } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", "==2.8.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--no-mask-deck-env-vars-value", "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputUnMaskedJSON, out) + }) + } } // test scope: // - 3.x -func Test_Diff_Unasked_NewerThan3x(t *testing.T) { +func Test_Diff_Unmasked_NewerThan3x(t *testing.T) { tests := []struct { name string initialStateFile string @@ -251,8 +699,7 @@ func Test_Diff_Unasked_NewerThan3x(t *testing.T) { t.Setenv(k, v) } runWhen(t, "kong", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // initialize state assert.NoError(t, sync(tc.initialStateFile)) @@ -262,4 +709,36 @@ func Test_Diff_Unasked_NewerThan3x(t *testing.T) { assert.Equal(t, expectedOutputUnMasked, out) }) } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", ">=3.0.0 <3.1.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--no-mask-deck-env-vars-value", "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputUnMaskedJSON30x, out) + }) + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + runWhen(t, "kong", ">=3.1.0 <3.4.0") + setup(t) + + // initialize state + assert.NoError(t, sync(tc.initialStateFile)) + + out, err := diff(tc.stateFile, "--no-mask-deck-env-vars-value", "--json-output") + assert.NoError(t, err) + assert.Equal(t, expectedOutputUnMaskedJSON, out) + }) + } } diff --git a/tests/integration/dump_test.go b/tests/integration/dump_test.go index 015da2835..bda8df6a0 100644 --- a/tests/integration/dump_test.go +++ b/tests/integration/dump_test.go @@ -23,8 +23,7 @@ func Test_Dump_SelectTags_30(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.0.0 <3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) assert.NoError(t, sync(tc.stateFile)) @@ -57,8 +56,7 @@ func Test_Dump_SelectTags_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) assert.NoError(t, sync(tc.stateFile)) @@ -82,25 +80,55 @@ func Test_Dump_SkipConsumers(t *testing.T) { stateFile string expectedFile string skipConsumers bool + runWhen func(t *testing.T) }{ { - name: "dump with skip-consumers", + name: "3.2 & 3.3 dump with skip-consumers", stateFile: "testdata/dump/002-skip-consumers/kong.yaml", expectedFile: "testdata/dump/002-skip-consumers/expected.yaml", skipConsumers: true, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.2.0 <3.4.0") }, }, { - name: "dump with no skip-consumers", + name: "3.2 & 3.3 dump with no skip-consumers", stateFile: "testdata/dump/002-skip-consumers/kong.yaml", expectedFile: "testdata/dump/002-skip-consumers/expected-no-skip.yaml", skipConsumers: false, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.2.0 <3.4.0") }, + }, + { + name: "3.4 dump with skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected.yaml", + skipConsumers: true, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.4.0 <3.5.0") }, + }, + { + name: "3.4 dump with no skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected-no-skip-34.yaml", + skipConsumers: false, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.4.0 <3.5.0") }, + }, + { + name: "3.5 dump with skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected.yaml", + skipConsumers: true, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.5.0") }, + }, + { + name: "3.5 dump with no skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected-no-skip-35.yaml", + skipConsumers: false, + runWhen: func(t *testing.T) { runWhen(t, "enterprise", ">=3.5.0") }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "enterprise", ">=3.2.0") - teardown := setup(t) - defer teardown(t) + tc.runWhen(t) + setup(t) assert.NoError(t, sync(tc.stateFile)) @@ -122,7 +150,57 @@ func Test_Dump_SkipConsumers(t *testing.T) { expected, err := readFile(tc.expectedFile) assert.NoError(t, err) - assert.Equal(t, output, expected) + assert.Equal(t, expected, output) + }) + } +} + +func Test_Dump_SkipConsumers_Konnect(t *testing.T) { + tests := []struct { + name string + stateFile string + expectedFile string + skipConsumers bool + }{ + { + name: "dump with skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected_konnect.yaml", + skipConsumers: true, + }, + { + name: "dump with no skip-consumers", + stateFile: "testdata/dump/002-skip-consumers/kong34.yaml", + expectedFile: "testdata/dump/002-skip-consumers/expected-no-skip_konnect.yaml", + skipConsumers: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhenKonnect(t) + setup(t) + + assert.NoError(t, sync(tc.stateFile)) + + var ( + output string + err error + ) + if tc.skipConsumers { + output, err = dump( + "--skip-consumers", + "-o", "-", + ) + } else { + output, err = dump( + "-o", "-", + ) + } + assert.NoError(t, err) + + expected, err := readFile(tc.expectedFile) + assert.NoError(t, err) + assert.Equal(t, expected, output) }) } } diff --git a/tests/integration/ping_test.go b/tests/integration/ping_test.go index 748f8d01c..49561e76c 100644 --- a/tests/integration/ping_test.go +++ b/tests/integration/ping_test.go @@ -3,14 +3,24 @@ package integration import ( + "os" "testing" "github.com/stretchr/testify/require" ) func Test_KonnectPing(t *testing.T) { - t.Run("konnect ping", func(t *testing.T) { + t.Run("konnect ping - email/password", func(t *testing.T) { runWhen(t, "konnect", "") - require.NoError(t, ping()) + require.NoError(t, ping( + "--konnect-email", os.Getenv("DECK_KONNECT_EMAIL"), + "--konnect-password", os.Getenv("DECK_KONNECT_PASSWORD"), + )) + }) + t.Run("konnect ping - token", func(t *testing.T) { + runWhen(t, "konnect", "") + require.NoError(t, ping( + "--konnect-token", os.Getenv("DECK_KONNECT_TOKEN"), + )) }) } diff --git a/tests/integration/reset_test.go b/tests/integration/reset_test.go index aea8c56f3..28ed397bd 100644 --- a/tests/integration/reset_test.go +++ b/tests/integration/reset_test.go @@ -62,8 +62,7 @@ func Test_Reset_SkipCACert_2x(t *testing.T) { // here because the schema changed and the entities aren't the same // across all versions, even though the skip functionality works the same. runWhen(t, "kong", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) reset(t, "--skip-ca-certificates") @@ -99,8 +98,7 @@ func Test_Reset_SkipCACert_3x(t *testing.T) { // here because the schema changed and the entities aren't the same // across all versions, even though the skip functionality works the same. runWhen(t, "kong", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) reset(t, "--skip-ca-certificates") diff --git a/tests/integration/sync_test.go b/tests/integration/sync_test.go index 598266d7e..5e425a7f0 100644 --- a/tests/integration/sync_test.go +++ b/tests/integration/sync_test.go @@ -6,6 +6,7 @@ import ( "crypto/tls" "crypto/x509" "errors" + "fmt" "net/http" "testing" "time" @@ -627,10 +628,10 @@ var ( { Name: kong.String("rate-limiting-advanced"), Config: kong.Configuration{ - "limit": []*int32{int32p(7)}, - "retry_after_jitter_max": int32p(1), - "window_size": []*int32{int32p(60)}, - "window_type": kong.String("sliding"), + "limit": []any{float64(7)}, + "retry_after_jitter_max": float64(1), + "window_size": []any{float64(60)}, + "window_type": "sliding", }, ConsumerGroup: &kong.ConsumerGroup{ ID: kong.String("521a90ad-36cb-4e31-a5db-1d979aee40d1"), @@ -652,10 +653,10 @@ var ( { Name: kong.String("rate-limiting-advanced"), Config: kong.Configuration{ - "limit": []*int32{int32p(10)}, - "retry_after_jitter_max": int32p(1), - "window_size": []*int32{int32p(60)}, - "window_type": kong.String("sliding"), + "limit": []any{float64(10)}, + "retry_after_jitter_max": float64(1), + "window_size": []any{float64(60)}, + "window_type": "sliding", }, ConsumerGroup: &kong.ConsumerGroup{ ID: kong.String("92177268-b134-42f9-909a-36f9d2d3d5e7"), @@ -821,157 +822,343 @@ var ( Protocols: []*string{kong.String("http"), kong.String("https")}, }, } -) - -// test scope: -// - 1.4.3 -func Test_Sync_ServicesRoutes_Till_1_4_3(t *testing.T) { - // setup stage - client, err := getTestClient() - if err != nil { - t.Errorf(err.Error()) - } - - // ignore entities fields based on Kong version - ignoreFields := []cmp.Option{ - cmpopts.IgnoreFields(kong.Route{}, "Service"), - } - tests := []struct { - name string - kongFile string - expectedState utils.KongRawState - }{ + consumerGroupScopedPlugins = []*kong.Plugin{ { - name: "creates a service", - kongFile: "testdata/sync/001-create-a-service/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("77e6691d-67c0-446a-9401-27be2b141aae"), }, - }, - { - name: "create services and routes", - kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, - Routes: route1_143, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(10)}, + "namespace": string("gold"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", "<=1.4.3") - teardown := setup(t) - defer teardown(t) - - sync(tc.kongFile) - testKongState(t, client, false, tc.expectedState, ignoreFields) - }) - } -} - -// test scope: -// - 1.5.1 -// - 1.5.0.11+enterprise -func Test_Sync_ServicesRoutes_Till_1_5_1(t *testing.T) { - // setup stage - client, err := getTestClient() - if err != nil { - t.Errorf(err.Error()) - } - - tests := []struct { - name string - kongFile string - expectedState utils.KongRawState - }{ { - name: "creates a service", - kongFile: "testdata/sync/001-create-a-service/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("5bcbd3a7-030b-4310-bd1d-2721ff85d236"), }, - }, - { - name: "create services and routes", - kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, - Routes: route1_151, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(7)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", ">1.4.3 <=1.5.1") - teardown := setup(t) - defer teardown(t) - - sync(tc.kongFile) - testKongState(t, client, false, tc.expectedState, nil) - }) - } -} - -// test scope: -// - 2.0.5 -// - 2.1.4 -func Test_Sync_ServicesRoutes_From_2_0_5_To_2_1_4(t *testing.T) { - // setup stage - client, err := getTestClient() - if err != nil { - t.Errorf(err.Error()) - } - - tests := []struct { - name string - kongFile string - expectedState utils.KongRawState - }{ { - name: "creates a service", - kongFile: "testdata/sync/001-create-a-service/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, + Name: kong.String("rate-limiting-advanced"), + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(5)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, }, { - name: "create services and routes", - kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", - expectedState: utils.KongRawState{ - Services: svc1, - Routes: route1_205_214, + Name: kong.String("key-auth"), + Config: kong.Configuration{ + "anonymous": nil, + "hide_credentials": false, + "key_in_body": false, + "key_in_header": true, + "key_in_query": true, + "key_names": []interface{}{"apikey"}, + "run_on_preflight": true, }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("http"), kong.String("https")}, }, } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", ">=2.0.5 <=2.1.4") - teardown := setup(t) - defer teardown(t) - - sync(tc.kongFile) - testKongState(t, client, false, tc.expectedState, nil) - }) - } -} -// test scope: -// - 2.2.2 -// - 2.3.3 -// - 2.4.1 -// - 2.5.1 -// - 2.6.0 -// - 2.2.1.3+enterprise -// - 2.3.3.4+enterprise -// - 2.4.1.3+enterprise -// - 2.5.1.2+enterprise -func Test_Sync_ServicesRoutes_From_2_2_1_to_2_6_0(t *testing.T) { - // setup stage - client, err := getTestClient() - if err != nil { - t.Errorf(err.Error()) - } + consumerGroupScopedPlugins35x = []*kong.Plugin{ + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("77e6691d-67c0-446a-9401-27be2b141aae"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(10)}, + "namespace": string("gold"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(256), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("5bcbd3a7-030b-4310-bd1d-2721ff85d236"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(7)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(256), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + { + Name: kong.String("rate-limiting-advanced"), + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(5)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(256), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + { + Name: kong.String("key-auth"), + Config: kong.Configuration{ + "anonymous": nil, + "hide_credentials": false, + "key_in_body": false, + "key_in_header": true, + "key_in_query": true, + "key_names": []interface{}{"apikey"}, + "run_on_preflight": true, + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("http"), kong.String("https")}, + }, + } +) + +// test scope: +// - 1.4.3 +func Test_Sync_ServicesRoutes_Till_1_4_3(t *testing.T) { + // setup stage + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + // ignore entities fields based on Kong version + ignoreFields := []cmp.Option{ + cmpopts.IgnoreFields(kong.Route{}, "Service"), + } tests := []struct { name string @@ -990,28 +1177,25 @@ func Test_Sync_ServicesRoutes_From_2_2_1_to_2_6_0(t *testing.T) { kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", expectedState: utils.KongRawState{ Services: svc1, - Routes: route1_20x, + Routes: route1_143, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", ">2.2.1 <=2.6.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "kong", "<=1.4.3") + setup(t) sync(tc.kongFile) - testKongState(t, client, false, tc.expectedState, nil) + testKongState(t, client, false, tc.expectedState, ignoreFields) }) } } // test scope: -// - 2.7.0 -// - 2.6.0.2+enterprise -// - 2.7.0.0+enterprise -// - 2.8.0.0+enterprise -func Test_Sync_ServicesRoutes_From_2_6_9_Till_2_8_0(t *testing.T) { +// - 1.5.1 +// - 1.5.0.11+enterprise +func Test_Sync_ServicesRoutes_Till_1_5_1(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1027,23 +1211,22 @@ func Test_Sync_ServicesRoutes_From_2_6_9_Till_2_8_0(t *testing.T) { name: "creates a service", kongFile: "testdata/sync/001-create-a-service/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, + Services: svc1, }, }, { name: "create services and routes", kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, - Routes: route1_20x, + Services: svc1, + Routes: route1_151, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", ">2.6.9 <3.0.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "kong", ">1.4.3 <=1.5.1") + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1052,8 +1235,9 @@ func Test_Sync_ServicesRoutes_From_2_6_9_Till_2_8_0(t *testing.T) { } // test scope: -// - 3.x -func Test_Sync_ServicesRoutes_From_3x(t *testing.T) { +// - 2.0.5 +// - 2.1.4 +func Test_Sync_ServicesRoutes_From_2_0_5_To_2_1_4(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1067,25 +1251,24 @@ func Test_Sync_ServicesRoutes_From_3x(t *testing.T) { }{ { name: "creates a service", - kongFile: "testdata/sync/001-create-a-service/kong3x.yaml", + kongFile: "testdata/sync/001-create-a-service/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, + Services: svc1, }, }, { name: "create services and routes", - kongFile: "testdata/sync/002-create-services-and-routes/kong3x.yaml", + kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, - Routes: route1_20x, + Services: svc1, + Routes: route1_205_214, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "kong", ">=2.0.5 <=2.1.4") + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1094,8 +1277,16 @@ func Test_Sync_ServicesRoutes_From_3x(t *testing.T) { } // test scope: -// - konnect -func Test_Sync_ServicesRoutes_Konnect(t *testing.T) { +// - 2.2.2 +// - 2.3.3 +// - 2.4.1 +// - 2.5.1 +// - 2.6.0 +// - 2.2.1.3+enterprise +// - 2.3.3.4+enterprise +// - 2.4.1.3+enterprise +// - 2.5.1.2+enterprise +func Test_Sync_ServicesRoutes_From_2_2_1_to_2_6_0(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1109,25 +1300,24 @@ func Test_Sync_ServicesRoutes_Konnect(t *testing.T) { }{ { name: "creates a service", - kongFile: "testdata/sync/001-create-a-service/kong3x.yaml", + kongFile: "testdata/sync/001-create-a-service/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, + Services: svc1, }, }, { name: "create services and routes", - kongFile: "testdata/sync/002-create-services-and-routes/kong3x.yaml", + kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", expectedState: utils.KongRawState{ - Services: svc1_207, + Services: svc1, Routes: route1_20x, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "konnect", "") - teardown := setup(t) - defer teardown(t) + runWhen(t, "kong", ">2.2.1 <=2.6.0") + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1136,8 +1326,11 @@ func Test_Sync_ServicesRoutes_Konnect(t *testing.T) { } // test scope: -// - 1.4.3 -func Test_Sync_BasicAuth_Plugin_1_4_3(t *testing.T) { +// - 2.7.0 +// - 2.6.0.2+enterprise +// - 2.7.0.0+enterprise +// - 2.8.0.0+enterprise +func Test_Sync_ServicesRoutes_From_2_6_9_Till_2_8_0(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1145,24 +1338,30 @@ func Test_Sync_BasicAuth_Plugin_1_4_3(t *testing.T) { } tests := []struct { - name string - kongFile string - initialKongFile string - expectedState utils.KongRawState + name string + kongFile string + expectedState utils.KongRawState }{ { - name: "create a plugin", - kongFile: "testdata/sync/003-create-a-plugin/kong.yaml", + name: "creates a service", + kongFile: "testdata/sync/001-create-a-service/kong.yaml", expectedState: utils.KongRawState{ - Plugins: plugin_143_151, + Services: svc1_207, + }, + }, + { + name: "create services and routes", + kongFile: "testdata/sync/002-create-services-and-routes/kong.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + Routes: route1_20x, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "kong", "==1.4.3") - teardown := setup(t) - defer teardown(t) + runWhen(t, "kong", ">2.6.9 <3.0.0") + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1171,8 +1370,8 @@ func Test_Sync_BasicAuth_Plugin_1_4_3(t *testing.T) { } // test scope: -// - 1.5.0.11+enterprise -func Test_Sync_BasicAuth_Plugin_Earlier_Than_1_5_1(t *testing.T) { +// - 3.x +func Test_Sync_ServicesRoutes_From_3x(t *testing.T) { // setup stage client, err := getTestClient() if err != nil { @@ -1180,24 +1379,139 @@ func Test_Sync_BasicAuth_Plugin_Earlier_Than_1_5_1(t *testing.T) { } tests := []struct { - name string - kongFile string - initialKongFile string - expectedState utils.KongRawState + name string + kongFile string + expectedState utils.KongRawState }{ { - name: "create a plugin", - kongFile: "testdata/sync/003-create-a-plugin/kong.yaml", + name: "creates a service", + kongFile: "testdata/sync/001-create-a-service/kong3x.yaml", expectedState: utils.KongRawState{ - Plugins: plugin, + Services: svc1_207, }, }, - } + { + name: "create services and routes", + kongFile: "testdata/sync/002-create-services-and-routes/kong3x.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + Routes: route1_20x, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhenKongOrKonnect(t, ">=3.0.0") + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} + +// test scope: +// - konnect +func Test_Sync_ServicesRoutes_Konnect(t *testing.T) { + // setup stage + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + tests := []struct { + name string + kongFile string + expectedState utils.KongRawState + }{ + { + name: "creates a service", + kongFile: "testdata/sync/001-create-a-service/kong3x.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + }, + }, + { + name: "create services and routes", + kongFile: "testdata/sync/002-create-services-and-routes/kong3x.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + Routes: route1_20x, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "konnect", "") + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} + +// test scope: +// - 1.4.3 +func Test_Sync_BasicAuth_Plugin_1_4_3(t *testing.T) { + // setup stage + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + tests := []struct { + name string + kongFile string + initialKongFile string + expectedState utils.KongRawState + }{ + { + name: "create a plugin", + kongFile: "testdata/sync/003-create-a-plugin/kong.yaml", + expectedState: utils.KongRawState{ + Plugins: plugin_143_151, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "kong", "==1.4.3") + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} + +// test scope: +// - 1.5.0.11+enterprise +func Test_Sync_BasicAuth_Plugin_Earlier_Than_1_5_1(t *testing.T) { + // setup stage + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + tests := []struct { + name string + kongFile string + initialKongFile string + expectedState utils.KongRawState + }{ + { + name: "create a plugin", + kongFile: "testdata/sync/003-create-a-plugin/kong.yaml", + expectedState: utils.KongRawState{ + Plugins: plugin, + }, + }, + } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "<1.5.1 !1.4.3") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1231,8 +1545,7 @@ func Test_Sync_BasicAuth_Plugin_1_5_1(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "==1.5.1") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1281,8 +1594,7 @@ func Test_Sync_BasicAuth_Plugin_From_2_0_5_Till_2_8_0(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.0.5 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1316,8 +1628,7 @@ func Test_Sync_BasicAuth_Plugin_From_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1351,8 +1662,7 @@ func Test_Sync_BasicAuth_Plugin_Konnect(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "konnect", "") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1394,8 +1704,7 @@ func Test_Sync_Upstream_Target_Till_1_5_2(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "<=1.5.2") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, ignoreFields) @@ -1445,8 +1754,7 @@ func Test_Sync_Upstream_Target_From_2x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.1.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1481,8 +1789,7 @@ func Test_Sync_Upstream_Target_From_30(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.0.0 <3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1517,8 +1824,7 @@ func Test_Sync_Upstream_Target_From_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1553,8 +1859,7 @@ func Test_Sync_Upstream_Target_Konnect(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "konnect", "") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1597,8 +1902,7 @@ func Test_Sync_Upstreams_Target_ZeroWeight_2x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.4.1 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1633,8 +1937,7 @@ func Test_Sync_Upstreams_Target_ZeroWeight_30(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.0.0 <3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1669,8 +1972,7 @@ func Test_Sync_Upstreams_Target_ZeroWeight_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.1.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1705,8 +2007,7 @@ func Test_Sync_Upstreams_Target_ZeroWeight_Konnect(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "konnect", "") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1745,8 +2046,7 @@ func Test_Sync_RateLimitingPlugin(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "==2.7.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1789,8 +2089,7 @@ func Test_Sync_FillDefaults_Earlier_Than_1_5_1(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", "<1.5.1 !1.4.3") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, ignoreFields) @@ -1828,8 +2127,7 @@ func Test_Sync_FillDefaults_From_2_0_5_To_2_1_4(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.0.5 <=2.1.4") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1874,8 +2172,7 @@ func Test_Sync_FillDefaults_From_2_2_1_to_2_6_0(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">2.2.1 <=2.6.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1915,8 +2212,7 @@ func Test_Sync_FillDefaults_From_2_6_9(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">2.6.9 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -1952,8 +2248,7 @@ func Test_Sync_SkipCACert_2x(t *testing.T) { // here because the schema changed and the entities aren't the same // across all versions, even though the skip functionality works the same. runWhen(t, "kong", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile, "--skip-ca-certificates") testKongState(t, client, false, tc.expectedState, nil) @@ -1989,8 +2284,7 @@ func Test_Sync_SkipCACert_3x(t *testing.T) { // here because the schema changed and the entities aren't the same // across all versions, even though the skip functionality works the same. runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile, "--skip-ca-certificates") testKongState(t, client, false, tc.expectedState, nil) @@ -2095,8 +2389,7 @@ func Test_Sync_RBAC_2x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "enterprise", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile, "--rbac-resources-only") testKongState(t, client, false, tc.expectedState, nil) @@ -2201,8 +2494,7 @@ func Test_Sync_RBAC_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "enterprise", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile, "--rbac-resources-only") testKongState(t, client, false, tc.expectedState, nil) @@ -2235,8 +2527,7 @@ func Test_Sync_Create_Route_With_Service_Name_Reference_2x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2269,8 +2560,7 @@ func Test_Sync_Create_Route_With_Service_Name_Reference_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2308,8 +2598,7 @@ func Test_Sync_PluginsOnEntitiesTill_3_0_0(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.8.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2346,8 +2635,7 @@ func Test_Sync_PluginsOnEntitiesFrom_3_0_0(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2405,8 +2693,7 @@ func Test_Sync_PluginOrdering(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "enterprise", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2433,8 +2720,7 @@ func Test_Sync_Unsupported_Formats(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) err := sync(tc.kongFile) assert.Equal(t, err, tc.expectedError) @@ -2583,8 +2869,7 @@ func Test_Sync_Vault(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "enterprise", ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2672,8 +2957,7 @@ func Test_Sync_UpdateUsernameInConsumerWithCustomID(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.8.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -2717,8 +3001,7 @@ func Test_Sync_UpdateConsumerWithCustomID(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "kong", ">=2.8.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -2762,8 +3045,7 @@ func Test_Sync_UpdateUsernameInConsumerWithCustomID_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -2807,8 +3089,7 @@ func Test_Sync_UpdateConsumerWithCustomID_3x(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -2851,8 +3132,7 @@ func Test_Sync_ConsumerGroupsTill30(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "enterprise", ">=2.7.0 <3.0.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -2894,9 +3174,8 @@ func Test_Sync_ConsumerGroups_31(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "enterprise", ">=3.1.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "enterprise", "==3.1.0") + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -2983,9 +3262,8 @@ func Test_Sync_ConsumerGroupsRLAFrom31(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "enterprise", "==3.0.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "enterprise", ">=3.0.0 <3.1.0") + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -3069,21 +3347,11 @@ func Test_Sync_ConsumerGroupsKonnect(t *testing.T) { ConsumerGroups: consumerGroupsWithTags, }, }, - { - name: "creates consumer groups and plugin", - kongFile: "testdata/sync/016-consumer-groups-and-plugins/kong3x.yaml", - kongFileInitial: "testdata/sync/016-consumer-groups-and-plugins/kong3x-initial.yaml", - expectedState: utils.KongRawState{ - Consumers: consumerGroupsConsumers, - ConsumerGroups: consumerGroupsWithRLAKonnect, - }, - }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhen(t, "konnect", "") - teardown := setup(t) - defer teardown(t) + setup(t) // set up initial state sync(tc.kongFileInitial) @@ -3167,8 +3435,7 @@ func Test_Sync_PluginInstanceName(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { runWhenKongOrKonnect(t, ">=3.2.0") - teardown := setup(t) - defer teardown(t) + setup(t) sync(tc.kongFile) testKongState(t, client, false, tc.expectedState, nil) @@ -3177,7 +3444,8 @@ func Test_Sync_PluginInstanceName(t *testing.T) { } // test scope: -// - 3.2.0+ +// - 3.2.x +// - 3.3.x func Test_Sync_SkipConsumers(t *testing.T) { // setup stage client, err := getTestClient() @@ -3212,9 +3480,8 @@ func Test_Sync_SkipConsumers(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - runWhen(t, "enterprise", ">=3.2.0") - teardown := setup(t) - defer teardown(t) + runWhen(t, "enterprise", ">=3.2.0 <3.4.0") + setup(t) if tc.skipConsumers { sync(tc.kongFile, "--skip-consumers") @@ -3226,103 +3493,448 @@ func Test_Sync_SkipConsumers(t *testing.T) { } } -// In the tests we're concerned only with the IDs and names of the entities we'll ignore other fields when comparing states. -var ignoreFieldsIrrelevantForIDsTests = []cmp.Option{ - cmpopts.IgnoreFields( - kong.Plugin{}, - "Config", - "Protocols", - "Enabled", - ), - cmpopts.IgnoreFields( - kong.Service{}, - "ConnectTimeout", - "Enabled", - "Host", - "Port", - "Protocol", - "ReadTimeout", - "WriteTimeout", - "Retries", - ), - cmpopts.IgnoreFields( - kong.Route{}, - "Paths", - "PathHandling", - "PreserveHost", - "Protocols", - "RegexPriority", - "StripPath", - "HTTPSRedirectStatusCode", - "Sources", - "Destinations", - "RequestBuffering", - "ResponseBuffering", - ), -} - // test scope: -// - 3.0.0+ -// - konnect -func Test_Sync_ChangingIDsWhileKeepingNames(t *testing.T) { - runWhenKongOrKonnect(t, ">=3.0.0") - +// - 3.4.x +func Test_Sync_SkipConsumers_34x(t *testing.T) { + runWhen(t, "enterprise", ">=3.4.0 <3.5.0") + // setup stage client, err := getTestClient() if err != nil { t.Errorf(err.Error()) } - // These are the IDs that should be present in Kong after the second sync in all cases. - var ( - expectedServiceID = kong.String("98076db2-28b6-423b-ba39-a797193017f7") - expectedRouteID = kong.String("97b6a97e-f3f7-4c47-857a-7464cb9e202b") - expectedConsumerID = kong.String("9a1e49a8-2536-41fa-a4e9-605bf218a4fa") - ) - - // These are the entities that should be present in Kong after the second sync in all cases. - var ( - expectedService = &kong.Service{ - Name: kong.String("s1"), - ID: expectedServiceID, - } - - expectedRoute = &kong.Route{ - Name: kong.String("r1"), - ID: expectedRouteID, - Service: &kong.Service{ - ID: expectedServiceID, - }, - } - - expectedConsumer = &kong.Consumer{ - Username: kong.String("c1"), - ID: expectedConsumerID, - } - - expectedPlugins = []*kong.Plugin{ - { - Name: kong.String("rate-limiting"), - Route: &kong.Route{ - ID: expectedRouteID, - }, + tests := []struct { + name string + kongFile string + skipConsumers bool + expectedState utils.KongRawState + }{ + { + name: "skip-consumers successfully", + kongFile: "testdata/sync/019-skip-consumers/kong34.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, }, - { - Name: kong.String("rate-limiting"), - Service: &kong.Service{ - ID: expectedServiceID, + skipConsumers: true, + }, + { + name: "do not skip consumers successfully", + kongFile: "testdata/sync/019-skip-consumers/kong34.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + Consumers: consumerGroupsConsumers, + ConsumerGroups: []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + Tags: kong.StringSlice("tag1", "tag3"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + Tags: kong.StringSlice("tag1", "tag2"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, }, - }, - { - Name: kong.String("rate-limiting"), - Consumer: &kong.Consumer{ - ID: expectedConsumerID, + Plugins: []*kong.Plugin{ + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("77e6691d-67c0-446a-9401-27be2b141aae"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(10)}, + "namespace": string("gold"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("5bcbd3a7-030b-4310-bd1d-2721ff85d236"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(7)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": float64(-1), + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, }, }, - } - ) + skipConsumers: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + setup(t) - testCases := []struct { - name string + if tc.skipConsumers { + sync(tc.kongFile, "--skip-consumers") + } else { + sync(tc.kongFile) + } + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} + +// test scope: +// - konnect +func Test_Sync_SkipConsumers_Konnect(t *testing.T) { + runWhenKonnect(t) + // setup stage + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + tests := []struct { + name string + kongFile string + skipConsumers bool + expectedState utils.KongRawState + }{ + { + name: "skip-consumers successfully", + kongFile: "testdata/sync/019-skip-consumers/kong34.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + }, + skipConsumers: true, + }, + { + name: "do not skip consumers successfully", + kongFile: "testdata/sync/019-skip-consumers/kong34.yaml", + expectedState: utils.KongRawState{ + Services: svc1_207, + Consumers: consumerGroupsConsumers, + ConsumerGroups: []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + Tags: kong.StringSlice("tag1", "tag3"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + Tags: kong.StringSlice("tag1", "tag2"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, + }, + Plugins: []*kong.Plugin{ + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("77e6691d-67c0-446a-9401-27be2b141aae"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(10)}, + "namespace": string("gold"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": nil, + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + { + Name: kong.String("rate-limiting-advanced"), + ConsumerGroup: &kong.ConsumerGroup{ + ID: kong.String("5bcbd3a7-030b-4310-bd1d-2721ff85d236"), + }, + Config: kong.Configuration{ + "consumer_groups": nil, + "dictionary_name": string("kong_rate_limiting_counters"), + "disable_penalty": bool(false), + "enforce_consumer_groups": bool(false), + "error_code": float64(429), + "error_message": string("API rate limit exceeded"), + "header_name": nil, + "hide_client_headers": bool(false), + "identifier": string("consumer"), + "limit": []any{float64(7)}, + "namespace": string("silver"), + "path": nil, + "redis": map[string]any{ + "cluster_addresses": nil, + "connect_timeout": nil, + "database": float64(0), + "host": nil, + "keepalive_backlog": nil, + "keepalive_pool_size": float64(30), + "password": nil, + "port": nil, + "read_timeout": nil, + "send_timeout": nil, + "sentinel_addresses": nil, + "sentinel_master": nil, + "sentinel_password": nil, + "sentinel_role": nil, + "sentinel_username": nil, + "server_name": nil, + "ssl": false, + "ssl_verify": false, + "timeout": float64(2000), + "username": nil, + }, + "retry_after_jitter_max": float64(1), + "strategy": string("local"), + "sync_rate": nil, + "window_size": []any{float64(60)}, + "window_type": string("sliding"), + }, + Enabled: kong.Bool(true), + Protocols: []*string{kong.String("grpc"), kong.String("grpcs"), kong.String("http"), kong.String("https")}, + }, + }, + }, + skipConsumers: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "enterprise", ">=3.2.0") + setup(t) + + if tc.skipConsumers { + sync(tc.kongFile, "--skip-consumers") + } else { + sync(tc.kongFile) + } + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} + +// In the tests we're concerned only with the IDs and names of the entities we'll ignore other fields when comparing states. +var ignoreFieldsIrrelevantForIDsTests = []cmp.Option{ + cmpopts.IgnoreFields( + kong.Plugin{}, + "Config", + "Protocols", + "Enabled", + ), + cmpopts.IgnoreFields( + kong.Service{}, + "ConnectTimeout", + "Enabled", + "Host", + "Port", + "Protocol", + "ReadTimeout", + "WriteTimeout", + "Retries", + ), + cmpopts.IgnoreFields( + kong.Route{}, + "Paths", + "PathHandling", + "PreserveHost", + "Protocols", + "RegexPriority", + "StripPath", + "HTTPSRedirectStatusCode", + "Sources", + "Destinations", + "RequestBuffering", + "ResponseBuffering", + ), +} + +// test scope: +// - 3.0.0+ +// - konnect +func Test_Sync_ChangingIDsWhileKeepingNames(t *testing.T) { + runWhenKongOrKonnect(t, ">=3.0.0") + + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + // These are the IDs that should be present in Kong after the second sync in all cases. + var ( + expectedServiceID = kong.String("98076db2-28b6-423b-ba39-a797193017f7") + expectedRouteID = kong.String("97b6a97e-f3f7-4c47-857a-7464cb9e202b") + expectedConsumerID = kong.String("9a1e49a8-2536-41fa-a4e9-605bf218a4fa") + ) + + // These are the entities that should be present in Kong after the second sync in all cases. + var ( + expectedService = &kong.Service{ + Name: kong.String("s1"), + ID: expectedServiceID, + } + + expectedRoute = &kong.Route{ + Name: kong.String("r1"), + ID: expectedRouteID, + Service: &kong.Service{ + ID: expectedServiceID, + }, + } + + expectedConsumer = &kong.Consumer{ + Username: kong.String("c1"), + ID: expectedConsumerID, + } + + expectedPlugins = []*kong.Plugin{ + { + Name: kong.String("rate-limiting"), + Route: &kong.Route{ + ID: expectedRouteID, + }, + }, + { + Name: kong.String("rate-limiting"), + Service: &kong.Service{ + ID: expectedServiceID, + }, + }, + { + Name: kong.String("rate-limiting"), + Consumer: &kong.Consumer{ + ID: expectedConsumerID, + }, + }, + } + ) + + testCases := []struct { + name string beforeConfig string }{ { @@ -3341,8 +3953,7 @@ func Test_Sync_ChangingIDsWhileKeepingNames(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - teardown := setup(t) - defer teardown(t) + setup(t) // First, create the entities with the original IDs. err = sync(tc.beforeConfig) @@ -3368,6 +3979,7 @@ func Test_Sync_ChangingIDsWhileKeepingNames(t *testing.T) { // - konnect func Test_Sync_UpdateWithExplicitIDs(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") + setup(t) client, err := getTestClient() if err != nil { @@ -3420,6 +4032,7 @@ func Test_Sync_UpdateWithExplicitIDs(t *testing.T) { // - konnect func Test_Sync_UpdateWithExplicitIDsWithNoNames(t *testing.T) { runWhenKongOrKonnect(t, ">=3.0.0") + setup(t) client, err := getTestClient() if err != nil { @@ -3458,3 +4071,502 @@ func Test_Sync_UpdateWithExplicitIDsWithNoNames(t *testing.T) { }, }, ignoreFieldsIrrelevantForIDsTests) } + +// test scope: +// - 3.0.0+ +// - konnect +func Test_Sync_CreateCertificateWithSNIs(t *testing.T) { + runWhenKongOrKonnect(t, ">=3.0.0") + setup(t) + + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + err = sync("testdata/sync/023-create-and-update-certificate-with-snis/initial.yaml") + require.NoError(t, err) + + // To ignore noise, we ignore the Key and Cert fields because they are not relevant for this test. + ignoredFields := []cmp.Option{ + cmpopts.IgnoreFields( + kong.Certificate{}, + "Key", + "Cert", + ), + } + + testKongState(t, client, false, utils.KongRawState{ + Certificates: []*kong.Certificate{ + { + ID: kong.String("c75a775b-3a32-4b73-8e05-f68169c23941"), + Tags: kong.StringSlice("before"), + }, + }, + SNIs: []*kong.SNI{ + { + Name: kong.String("example.com"), + Certificate: &kong.Certificate{ + ID: kong.String("c75a775b-3a32-4b73-8e05-f68169c23941"), + }, + }, + }, + }, ignoredFields) + + err = sync("testdata/sync/023-create-and-update-certificate-with-snis/update.yaml") + require.NoError(t, err) + + testKongState(t, client, false, utils.KongRawState{ + Certificates: []*kong.Certificate{ + { + ID: kong.String("c75a775b-3a32-4b73-8e05-f68169c23941"), + Tags: kong.StringSlice("after"), // Tag should be updated. + }, + }, + SNIs: []*kong.SNI{ + { + Name: kong.String("example.com"), + Certificate: &kong.Certificate{ + ID: kong.String("c75a775b-3a32-4b73-8e05-f68169c23941"), + }, + }, + }, + }, ignoredFields) +} + +// test scope: +// - 3.0.0+ +// - konnect +func Test_Sync_ConsumersWithCustomIDAndOrUsername(t *testing.T) { + runWhenKongOrKonnect(t, ">=3.0.0") + setup(t) + + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + + err = sync("testdata/sync/024-consumers-with-custom_id-and-username/kong3x.yaml") + require.NoError(t, err) + + testKongState(t, client, false, utils.KongRawState{ + Consumers: []*kong.Consumer{ + { + ID: kong.String("ce49186d-7670-445d-a218-897631b29ada"), + Username: kong.String("Foo"), + CustomID: kong.String("foo"), + }, + { + ID: kong.String("7820f383-7b77-4fcc-af7f-14ff3e256693"), + Username: kong.String("foo"), + CustomID: kong.String("bar"), + }, + { + ID: kong.String("18c62c3c-12cc-429a-8e5a-57f2c3691a6b"), + CustomID: kong.String("custom_id_only"), + }, + { + ID: kong.String("8ef278c9-48c1-43e1-b665-e9bc18fab4c8"), + Username: kong.String("username_only"), + }, + }, + }, nil) +} + +// This test has 2 goals: +// - make sure consumer groups scoped plugins can be configured correctly in Kong +// - the actual consumer groups functionality works once set +// +// This is achieved via configuring: +// - 3 consumers: +// - 1 belonging to Gold Consumer Group +// - 1 belonging to Silver Consumer Group +// - 1 not belonging to any Consumer Group +// +// - 3 key-auths, one for each consumer +// - 1 global key-auth plugin +// - 2 consumer group +// - 1 global RLA plugin +// - 2 RLA plugins, scoped to the related consumer groups +// - 1 service pointing to mockbin.org +// - 1 route proxying the above service +// +// Once the configuration is verified to be matching in Kong, +// we then check whether the specific RLA configuration is correctly applied: consumers +// not belonging to the consumer group should be limited to 5 requests +// every 30s, while consumers belonging to the 'gold' and 'silver' consumer groups +// should be allowed to run respectively 10 and 7 requests in the same timeframe. +// In order to make sure this is the case, we run requests in a loop +// for all consumers and then check at what point they start to receive 429. +func Test_Sync_ConsumerGroupsScopedPlugins(t *testing.T) { + const ( + maxGoldRequestsNumber = 10 + maxSilverRequestsNumber = 7 + maxRegularRequestsNumber = 5 + ) + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + tests := []struct { + name string + kongFile string + expectedState utils.KongRawState + }{ + { + name: "creates consumer groups scoped plugins", + kongFile: "testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml", + expectedState: utils.KongRawState{ + Consumers: consumerGroupsConsumers, + ConsumerGroups: []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, + }, + Plugins: consumerGroupScopedPlugins, + Services: svc1_207, + Routes: route1_20x, + KeyAuths: []*kong.KeyAuth{ + { + Consumer: &kong.Consumer{ + ID: kong.String("87095815-5395-454e-8c18-a11c9bc0ef04"), + }, + Key: kong.String("i-am-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("5a5b9369-baeb-4faa-a902-c40ccdc2928e"), + }, + Key: kong.String("i-am-not-so-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("e894ea9e-ad08-4acf-a960-5a23aa7701c7"), + }, + Key: kong.String("i-am-just-average"), + }, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "enterprise", ">=3.4.0 <3.5.0") + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + + // Kong proxy may need a bit to be ready. + time.Sleep(time.Second * 10) + + // build simple http client + client := &http.Client{} + + // test 'foo' consumer (part of 'gold' group) + req, err := http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-special") + n := 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxGoldRequestsNumber, n) + + // test 'bar' consumer (part of 'silver' group) + req, err = http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-not-so-special") + n = 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxSilverRequestsNumber, n) + + // test 'baz' consumer (not part of any group) + req, err = http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-just-average") + n = 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxRegularRequestsNumber, n) + }) + } +} + +func Test_Sync_ConsumerGroupsScopedPlugins_After350(t *testing.T) { + const ( + maxGoldRequestsNumber = 10 + maxSilverRequestsNumber = 7 + maxRegularRequestsNumber = 5 + ) + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + tests := []struct { + name string + kongFile string + expectedState utils.KongRawState + }{ + { + name: "creates consumer groups scoped plugins", + kongFile: "testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml", + expectedState: utils.KongRawState{ + Consumers: consumerGroupsConsumers, + ConsumerGroups: []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, + }, + Plugins: consumerGroupScopedPlugins35x, + Services: svc1_207, + Routes: route1_20x, + KeyAuths: []*kong.KeyAuth{ + { + Consumer: &kong.Consumer{ + ID: kong.String("87095815-5395-454e-8c18-a11c9bc0ef04"), + }, + Key: kong.String("i-am-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("5a5b9369-baeb-4faa-a902-c40ccdc2928e"), + }, + Key: kong.String("i-am-not-so-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("e894ea9e-ad08-4acf-a960-5a23aa7701c7"), + }, + Key: kong.String("i-am-just-average"), + }, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "enterprise", ">=3.5.0") + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + + // Kong proxy may need a bit to be ready. + time.Sleep(time.Second * 10) + + // build simple http client + client := &http.Client{} + + // test 'foo' consumer (part of 'gold' group) + req, err := http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-special") + n := 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxGoldRequestsNumber, n) + + // test 'bar' consumer (part of 'silver' group) + req, err = http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-not-so-special") + n = 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxSilverRequestsNumber, n) + + // test 'baz' consumer (not part of any group) + req, err = http.NewRequest("GET", "http://localhost:8000/r1", nil) + assert.NoError(t, err) + req.Header.Add("apikey", "i-am-just-average") + n = 0 + for n < 11 { + resp, err := client.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + if resp.StatusCode == http.StatusTooManyRequests { + break + } + n++ + } + assert.Equal(t, maxRegularRequestsNumber, n) + }) + } +} + +// test scope: +// - > 3.4.0 +func Test_Sync_ConsumerGroupsScopedPlugins_Post340(t *testing.T) { + tests := []struct { + name string + kongFile string + expectedError error + }{ + { + name: "attempt to create deprecated consumer groups configuration with Kong version >= 3.4.0 fails", + kongFile: "testdata/sync/017-consumer-groups-rla-application/kong3x.yaml", + expectedError: fmt.Errorf("building state: %v", utils.ErrorConsumerGroupUpgrade), + }, + { + name: "empty deprecated consumer groups configuration fields do not fail with Kong version >= 3.4.0", + kongFile: "testdata/sync/017-consumer-groups-rla-application/kong3x-empty-application.yaml", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "enterprise", ">=3.4.0") + setup(t) + + err := sync(tc.kongFile) + if tc.expectedError == nil { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedError.Error()) + } + }) + } +} + +func Test_Sync_ConsumerGroupsScopedPluginsKonnect(t *testing.T) { + client, err := getTestClient() + if err != nil { + t.Errorf(err.Error()) + } + tests := []struct { + name string + kongFile string + expectedState utils.KongRawState + }{ + { + name: "creates consumer groups scoped plugins", + kongFile: "testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml", + expectedState: utils.KongRawState{ + Consumers: consumerGroupsConsumers, + ConsumerGroups: []*kong.ConsumerGroupObject{ + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("silver"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("bar"), + }, + }, + }, + { + ConsumerGroup: &kong.ConsumerGroup{ + Name: kong.String("gold"), + }, + Consumers: []*kong.Consumer{ + { + Username: kong.String("foo"), + }, + }, + }, + }, + Plugins: consumerGroupScopedPlugins, + Services: svc1_207, + Routes: route1_20x, + KeyAuths: []*kong.KeyAuth{ + { + Consumer: &kong.Consumer{ + ID: kong.String("87095815-5395-454e-8c18-a11c9bc0ef04"), + }, + Key: kong.String("i-am-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("5a5b9369-baeb-4faa-a902-c40ccdc2928e"), + }, + Key: kong.String("i-am-not-so-special"), + }, + { + Consumer: &kong.Consumer{ + ID: kong.String("e894ea9e-ad08-4acf-a960-5a23aa7701c7"), + }, + Key: kong.String("i-am-just-average"), + }, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhenKonnect(t) + setup(t) + + sync(tc.kongFile) + testKongState(t, client, false, tc.expectedState, nil) + }) + } +} diff --git a/tests/integration/test_utils.go b/tests/integration/test_utils.go index 228db1e28..28d7c16c8 100644 --- a/tests/integration/test_utils.go +++ b/tests/integration/test_utils.go @@ -95,8 +95,16 @@ func sortSlices(x, y interface{}) bool { yName = *yEntity.Prefix case *kong.Consumer: yEntity := y.(*kong.Consumer) - xName = *xEntity.Username - yName = *yEntity.Username + if xEntity.Username != nil { + xName = *xEntity.Username + } else { + xName = *xEntity.ID + } + if yEntity.Username != nil { + yName = *yEntity.Username + } else { + yName = *yEntity.ID + } case *kong.ConsumerGroup: yEntity := y.(*kong.ConsumerGroup) xName = *xEntity.Name @@ -126,8 +134,8 @@ func sortSlices(x, y interface{}) bool { if xEntity.Consumer != nil { xName += *xEntity.Consumer.ID } - if xEntity.Consumer != nil { - xName += *xEntity.Consumer.ID + if xEntity.ConsumerGroup != nil { + xName += *xEntity.ConsumerGroup.ID } if yEntity.Route != nil { yName += *yEntity.Route.ID @@ -138,6 +146,9 @@ func sortSlices(x, y interface{}) bool { if yEntity.Consumer != nil { yName += *yEntity.Consumer.ID } + if yEntity.ConsumerGroup != nil { + yName += *yEntity.ConsumerGroup.ID + } } return xName < yName } @@ -207,12 +218,17 @@ func readFile(filepath string) (string, error) { return string(content), nil } -func setup(_ *testing.T) func(t *testing.T) { +// setup sets deck env variable to prevent analytics in tests and registers reset +// command with t.Cleanup(). +// +// NOTE: Can't be called with tests running t.Parallel() because of the usage +// of t.Setenv(). +func setup(t *testing.T) { // disable analytics for integration tests - os.Setenv("DECK_ANALYTICS", "off") - return func(t *testing.T) { + t.Setenv("DECK_ANALYTICS", "off") + t.Cleanup(func() { reset(t) - } + }) } func sync(kongFile string, opts ...string) error { diff --git a/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-34.yaml b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-34.yaml new file mode 100644 index 000000000..ee591dea4 --- /dev/null +++ b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-34.yaml @@ -0,0 +1,60 @@ +_format_version: "3.0" +consumer_groups: +- name: basic + plugins: + - config: + consumer_groups: null + dictionary_name: kong_rate_limiting_counters + disable_penalty: false + enforce_consumer_groups: false + error_code: 429 + error_message: API rate limit exceeded + header_name: null + hide_client_headers: false + identifier: consumer + limit: + - 30000 + namespace: basic + path: null + redis: + cluster_addresses: null + connect_timeout: null + database: 0 + host: null + keepalive_backlog: null + keepalive_pool_size: 30 + password: null + port: null + read_timeout: null + send_timeout: null + sentinel_addresses: null + sentinel_master: null + sentinel_password: null + sentinel_role: null + sentinel_username: null + server_name: null + ssl: false + ssl_verify: false + timeout: 2000 + username: null + retry_after_jitter_max: 0 + strategy: local + sync_rate: -1 + window_size: + - 2628000 + window_type: sliding + name: rate-limiting-advanced +consumers: +- groups: + - name: basic + username: foo +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 diff --git a/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-35.yaml b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-35.yaml new file mode 100644 index 000000000..86cd73d93 --- /dev/null +++ b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip-35.yaml @@ -0,0 +1,60 @@ +_format_version: "3.0" +consumer_groups: +- name: basic + plugins: + - config: + consumer_groups: null + dictionary_name: kong_rate_limiting_counters + disable_penalty: false + enforce_consumer_groups: false + error_code: 429 + error_message: API rate limit exceeded + header_name: null + hide_client_headers: false + identifier: consumer + limit: + - 30000 + namespace: basic + path: null + redis: + cluster_addresses: null + connect_timeout: null + database: 0 + host: null + keepalive_backlog: null + keepalive_pool_size: 256 + password: null + port: null + read_timeout: null + send_timeout: null + sentinel_addresses: null + sentinel_master: null + sentinel_password: null + sentinel_role: null + sentinel_username: null + server_name: null + ssl: false + ssl_verify: false + timeout: 2000 + username: null + retry_after_jitter_max: 0 + strategy: local + sync_rate: -1 + window_size: + - 2628000 + window_type: sliding + name: rate-limiting-advanced +consumers: +- groups: + - name: basic + username: foo +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 diff --git a/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip_konnect.yaml b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip_konnect.yaml new file mode 100644 index 000000000..b055aaeb1 --- /dev/null +++ b/tests/integration/testdata/dump/002-skip-consumers/expected-no-skip_konnect.yaml @@ -0,0 +1,62 @@ +_format_version: "3.0" +_konnect: + runtime_group_name: default +consumer_groups: +- name: basic + plugins: + - config: + consumer_groups: null + dictionary_name: kong_rate_limiting_counters + disable_penalty: false + enforce_consumer_groups: false + error_code: 429 + error_message: API rate limit exceeded + header_name: null + hide_client_headers: false + identifier: consumer + limit: + - 30000 + namespace: basic + path: null + redis: + cluster_addresses: null + connect_timeout: null + database: 0 + host: null + keepalive_backlog: null + keepalive_pool_size: 30 + password: null + port: null + read_timeout: null + send_timeout: null + sentinel_addresses: null + sentinel_master: null + sentinel_password: null + sentinel_role: null + sentinel_username: null + server_name: null + ssl: false + ssl_verify: false + timeout: 2000 + username: null + retry_after_jitter_max: 0 + strategy: local + sync_rate: null + window_size: + - 2628000 + window_type: sliding + name: rate-limiting-advanced +consumers: +- groups: + - name: basic + username: foo +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 diff --git a/tests/integration/testdata/dump/002-skip-consumers/expected_konnect.yaml b/tests/integration/testdata/dump/002-skip-consumers/expected_konnect.yaml new file mode 100644 index 000000000..d27edcddd --- /dev/null +++ b/tests/integration/testdata/dump/002-skip-consumers/expected_konnect.yaml @@ -0,0 +1,13 @@ +_format_version: "3.0" +_konnect: + runtime_group_name: default +services: +- connect_timeout: 60000 + enabled: true + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + write_timeout: 60000 diff --git a/tests/integration/testdata/dump/002-skip-consumers/kong34.yaml b/tests/integration/testdata/dump/002-skip-consumers/kong34.yaml new file mode 100644 index 000000000..5a04f7573 --- /dev/null +++ b/tests/integration/testdata/dump/002-skip-consumers/kong34.yaml @@ -0,0 +1,19 @@ +_format_version: "3.0" +consumer_groups: +- name: basic + plugins: + - config: + limit: + - 30000 + window_size: + - 2628000 + window_type: sliding + namespace: basic + name: rate-limiting-advanced +consumers: + - username: foo + groups: + - name: basic +services: +- name: svc1 + host: mockbin.org \ No newline at end of file diff --git a/tests/integration/testdata/sync/017-consumer-groups-rla-application/kong3x-empty-application.yaml b/tests/integration/testdata/sync/017-consumer-groups-rla-application/kong3x-empty-application.yaml new file mode 100644 index 000000000..cd5cde8a5 --- /dev/null +++ b/tests/integration/testdata/sync/017-consumer-groups-rla-application/kong3x-empty-application.yaml @@ -0,0 +1,48 @@ +_format_version: "3.0" +plugins: +- config: + consumer_groups: null + dictionary_name: kong_rate_limiting_counters + enforce_consumer_groups: false + header_name: null + hide_client_headers: false + identifier: consumer + limit: + - 5 + namespace: dNRC6xKsRL8Koc1uVYA4Nki6DLW7XIdx + path: null + redis: + cluster_addresses: null + connect_timeout: null + database: 0 + host: null + keepalive_backlog: null + keepalive_pool_size: 30 + password: null + port: null + read_timeout: null + send_timeout: null + sentinel_addresses: null + sentinel_master: null + sentinel_password: null + sentinel_role: null + sentinel_username: null + server_name: null + ssl: false + ssl_verify: false + timeout: 2000 + username: null + retry_after_jitter_max: 0 + strategy: local + sync_rate: -1 + window_size: + - 60 + window_type: sliding + enabled: true + name: rate-limiting-advanced + protocols: + - grpc + - grpcs + - http + - https + diff --git a/tests/integration/testdata/sync/019-skip-consumers/kong34.yaml b/tests/integration/testdata/sync/019-skip-consumers/kong34.yaml new file mode 100644 index 000000000..433ef4290 --- /dev/null +++ b/tests/integration/testdata/sync/019-skip-consumers/kong34.yaml @@ -0,0 +1,49 @@ +_format_version: "3.0" +consumer_groups: +- id: 77e6691d-67c0-446a-9401-27be2b141aae + name: gold + tags: + - tag1 + - tag2 + plugins: + - name: rate-limiting-advanced + config: + namespace: gold + limit: + - 10 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding +- id: 5bcbd3a7-030b-4310-bd1d-2721ff85d236 + name: silver + tags: + - tag1 + - tag3 + plugins: + - name: rate-limiting-advanced + config: + namespace: silver + limit: + - 7 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding +consumers: +- groups: + - name: silver + username: bar +- username: baz +- groups: + - name: gold + username: foo +services: +- connect_timeout: 60000 + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 diff --git a/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/initial.yaml b/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/initial.yaml new file mode 100644 index 000000000..65f2781fd --- /dev/null +++ b/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/initial.yaml @@ -0,0 +1,55 @@ +_format_version: "3.0" +certificates: + - id: 13c562a1-191c-4464-9b18-e5222b46035b + cert: | + -----BEGIN CERTIFICATE----- + MIIC1jCCAb4CCQCt23nwvxSCvjANBgkqhkiG9w0BAQsFADAtMRYwFAYDVQQDDA0q + LmV4YW1wbGUuY29tMRMwEQYDVQQKDAprb25naHEub3JnMB4XDTE4MTIzMTIwMTkw + MVoXDTE5MTIzMTIwMTkwMVowLTEWMBQGA1UEAwwNKi5leGFtcGxlLmNvbTETMBEG + A1UECgwKa29uZ2hxLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB + AKj/2r1AXo9x+2Csrd0SHbpnzuW+xYqgsd+YA9ZrZNV7SZGSbaZymsRMz8wg5OIU + iUik2GM1749/lYvojLFStBPy9UY/gd++5f3wLp4xHiI+IU2XQ97otXKGfyh36RmN + dKDqPLN8BG3R346s/y1GOulFvLthYmZVYF9ufHiqimfEDSbTt79P5C3X0Rw/afK1 + GjHEJPCB/XkZ6lkcEyL6LqZI5oBigDqa9hI/nWLxEzfm8pgosiS38p9TAijlOkpm + tX2p2b1pktlNIy3rxsqj6IynN9Wc7FpV1N4HoPKV7vQQ08hjwW6WfanVthaaJosj + Vr2TBCJ1ltAmsb+5B2VPYVkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAnByTyQfV + 3LkwuoWS57CWcqbNw/cHnv/ChzmIv+6mIXvDBSvCgrPZIWCpaCfYRG6R51E44fr/ + 8V1AKT0Zt15DjrXEEcIGQgsIDO91/wlL091fTAUzSbL0yt7HTlm8sX6xndPNAZrq + cfcIPVMxknfqPy2VqS4IrNC03pHkDKtokphBjVUlkiWsdcq+fHYbS2xL2d1Da/uN + hX/iwgo+v5gOF5xtaXx7D7L3Cf+MHb/MOXWPfYXNiTpSBVX8/Kx5RP+QLI16nWvw + lrijTlXZFR8NIZBrCo/QZ2cNbUAbN3R0n+/kMFubxBL8WEm6Qhi9jBjbJeDMspd8 + C+/TZJQMpx5vyA== + -----END CERTIFICATE----- + key: | + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCo/9q9QF6Pcftg + rK3dEh26Z87lvsWKoLHfmAPWa2TVe0mRkm2mcprETM/MIOTiFIlIpNhjNe+Pf5WL + 6IyxUrQT8vVGP4HfvuX98C6eMR4iPiFNl0Pe6LVyhn8od+kZjXSg6jyzfARt0d+O + rP8tRjrpRby7YWJmVWBfbnx4qopnxA0m07e/T+Qt19EcP2nytRoxxCTwgf15GepZ + HBMi+i6mSOaAYoA6mvYSP51i8RM35vKYKLIkt/KfUwIo5TpKZrV9qdm9aZLZTSMt + 68bKo+iMpzfVnOxaVdTeB6Dyle70ENPIY8Fuln2p1bYWmiaLI1a9kwQidZbQJrG/ + uQdlT2FZAgMBAAECggEAVnyRcda2Tcy0K7ZTR9aUlie370VhDN/OB7JhDGNreAEf + FjuMl+kAoUL5+OpAmB6QXzfVcXhRv+s4GiCJl9nORINK2Id5rIqiYwF+qgBS/o0z + N+UYm8QVz6Va/9fV1/jXXd5h8Cygi58jPH32HTJaxbSlsHNXCy3YIx6E3q/QIueR + 6ZdSXPqMEqxEU19M9jW8UeiRFrpmcyYxVpfxYIY/+O9lYjSpaeLs7hZeCP9PqWXA + Sxz2CnHZ8BcsDxAyuoHoVw+kjMpUMvA3sD4lwkV8BAYzfLmQf6PR83SFNsrE8XYu + /8WnQuCuytcl8Zg55R6tGCvf6Wyyf+MDRPwv/43QMQKBgQDbqK9Dq54k+EHgSNnP + K6AhNjFd6aqcNC1kom/sSlWBnuA/BEqJMECr8S2dYvzONUPPfX5NNUjB4Vw3Qw7a + pUgKuCQoVpzpZs5m1bk78itWDtA84LjkXfdejnUXVw/aVxLCM5QV9aEkm/dEWWMI + P1WTYVoWoZCLlEE08q0AvZQcdQKBgQDE9ZCmc6ncmhnQftuRj5PnXG2a79MLCT61 + sCEBDVvkcUJVqbzwGRLwRkdIzLgvmiuP+SukHgyfr8/RXG99xEW/q7NDrtEcqfXP + 19QXwOIp5NwDnOXyAlXiyZ50fCE2tSo2wP485+NIhmKj5Zt6y/DL6Qbc5k73XmK4 + KX5Ej15k1QKBgQCc6KeiIFLMt+Ze78tfORue/dZP7p3oDUGr1Hk9AnCIMlSfz1Hr + I+Per17VQaOzLcttyYhSYNDDZld4RlezCkQnHBkAE7bs53pjbSJv1vLr+5L3GdQZ + laIiEoNEE/YIExEcVrne4eKlgyAj2/JpLszThcRTzD+z5UibKQs6LzJBDQKBgDVa + dAGzCUt57w48nwvyQdWFgydaWef+bB9Zg8c+MCtUxuxfm4/Kqwetcff1hNtYPv60 + N68weKj1Pi1vhcAi3+YJA/mMrJbAL5dK1uhMVreUiEjuQpfpLAzQIv1Y9sJUFwhY + BUbIZhgqVyQguZptDmCeUj6aoL9/sOxESTEXSTG1AoGBAMQ5iJZMsdLCERv0+6Y1 + F/t/YSW8cugB3vdV9jHZuosoprz48p92pYP8OdQc70H5hZt53hoYNgYFSd+MU6H1 + hJCaXTsiP4IUmBjiwzSp3o1ctP8lWvnyJpAadYdDhaDaAAoaMjCo9cm5OMwc8t8x + hwAPXV2cgWH8fPcT9NLAcwWk + -----END PRIVATE KEY----- + tags: + - before + snis: + - name: example.com diff --git a/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/update.yaml b/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/update.yaml new file mode 100644 index 000000000..7256a7d1b --- /dev/null +++ b/tests/integration/testdata/sync/023-create-and-update-certificate-with-snis/update.yaml @@ -0,0 +1,55 @@ +_format_version: "3.0" +certificates: + - id: 13c562a1-191c-4464-9b18-e5222b46035b + cert: | + -----BEGIN CERTIFICATE----- + MIIC1jCCAb4CCQCt23nwvxSCvjANBgkqhkiG9w0BAQsFADAtMRYwFAYDVQQDDA0q + LmV4YW1wbGUuY29tMRMwEQYDVQQKDAprb25naHEub3JnMB4XDTE4MTIzMTIwMTkw + MVoXDTE5MTIzMTIwMTkwMVowLTEWMBQGA1UEAwwNKi5leGFtcGxlLmNvbTETMBEG + A1UECgwKa29uZ2hxLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB + AKj/2r1AXo9x+2Csrd0SHbpnzuW+xYqgsd+YA9ZrZNV7SZGSbaZymsRMz8wg5OIU + iUik2GM1749/lYvojLFStBPy9UY/gd++5f3wLp4xHiI+IU2XQ97otXKGfyh36RmN + dKDqPLN8BG3R346s/y1GOulFvLthYmZVYF9ufHiqimfEDSbTt79P5C3X0Rw/afK1 + GjHEJPCB/XkZ6lkcEyL6LqZI5oBigDqa9hI/nWLxEzfm8pgosiS38p9TAijlOkpm + tX2p2b1pktlNIy3rxsqj6IynN9Wc7FpV1N4HoPKV7vQQ08hjwW6WfanVthaaJosj + Vr2TBCJ1ltAmsb+5B2VPYVkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAnByTyQfV + 3LkwuoWS57CWcqbNw/cHnv/ChzmIv+6mIXvDBSvCgrPZIWCpaCfYRG6R51E44fr/ + 8V1AKT0Zt15DjrXEEcIGQgsIDO91/wlL091fTAUzSbL0yt7HTlm8sX6xndPNAZrq + cfcIPVMxknfqPy2VqS4IrNC03pHkDKtokphBjVUlkiWsdcq+fHYbS2xL2d1Da/uN + hX/iwgo+v5gOF5xtaXx7D7L3Cf+MHb/MOXWPfYXNiTpSBVX8/Kx5RP+QLI16nWvw + lrijTlXZFR8NIZBrCo/QZ2cNbUAbN3R0n+/kMFubxBL8WEm6Qhi9jBjbJeDMspd8 + C+/TZJQMpx5vyA== + -----END CERTIFICATE----- + key: | + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCo/9q9QF6Pcftg + rK3dEh26Z87lvsWKoLHfmAPWa2TVe0mRkm2mcprETM/MIOTiFIlIpNhjNe+Pf5WL + 6IyxUrQT8vVGP4HfvuX98C6eMR4iPiFNl0Pe6LVyhn8od+kZjXSg6jyzfARt0d+O + rP8tRjrpRby7YWJmVWBfbnx4qopnxA0m07e/T+Qt19EcP2nytRoxxCTwgf15GepZ + HBMi+i6mSOaAYoA6mvYSP51i8RM35vKYKLIkt/KfUwIo5TpKZrV9qdm9aZLZTSMt + 68bKo+iMpzfVnOxaVdTeB6Dyle70ENPIY8Fuln2p1bYWmiaLI1a9kwQidZbQJrG/ + uQdlT2FZAgMBAAECggEAVnyRcda2Tcy0K7ZTR9aUlie370VhDN/OB7JhDGNreAEf + FjuMl+kAoUL5+OpAmB6QXzfVcXhRv+s4GiCJl9nORINK2Id5rIqiYwF+qgBS/o0z + N+UYm8QVz6Va/9fV1/jXXd5h8Cygi58jPH32HTJaxbSlsHNXCy3YIx6E3q/QIueR + 6ZdSXPqMEqxEU19M9jW8UeiRFrpmcyYxVpfxYIY/+O9lYjSpaeLs7hZeCP9PqWXA + Sxz2CnHZ8BcsDxAyuoHoVw+kjMpUMvA3sD4lwkV8BAYzfLmQf6PR83SFNsrE8XYu + /8WnQuCuytcl8Zg55R6tGCvf6Wyyf+MDRPwv/43QMQKBgQDbqK9Dq54k+EHgSNnP + K6AhNjFd6aqcNC1kom/sSlWBnuA/BEqJMECr8S2dYvzONUPPfX5NNUjB4Vw3Qw7a + pUgKuCQoVpzpZs5m1bk78itWDtA84LjkXfdejnUXVw/aVxLCM5QV9aEkm/dEWWMI + P1WTYVoWoZCLlEE08q0AvZQcdQKBgQDE9ZCmc6ncmhnQftuRj5PnXG2a79MLCT61 + sCEBDVvkcUJVqbzwGRLwRkdIzLgvmiuP+SukHgyfr8/RXG99xEW/q7NDrtEcqfXP + 19QXwOIp5NwDnOXyAlXiyZ50fCE2tSo2wP485+NIhmKj5Zt6y/DL6Qbc5k73XmK4 + KX5Ej15k1QKBgQCc6KeiIFLMt+Ze78tfORue/dZP7p3oDUGr1Hk9AnCIMlSfz1Hr + I+Per17VQaOzLcttyYhSYNDDZld4RlezCkQnHBkAE7bs53pjbSJv1vLr+5L3GdQZ + laIiEoNEE/YIExEcVrne4eKlgyAj2/JpLszThcRTzD+z5UibKQs6LzJBDQKBgDVa + dAGzCUt57w48nwvyQdWFgydaWef+bB9Zg8c+MCtUxuxfm4/Kqwetcff1hNtYPv60 + N68weKj1Pi1vhcAi3+YJA/mMrJbAL5dK1uhMVreUiEjuQpfpLAzQIv1Y9sJUFwhY + BUbIZhgqVyQguZptDmCeUj6aoL9/sOxESTEXSTG1AoGBAMQ5iJZMsdLCERv0+6Y1 + F/t/YSW8cugB3vdV9jHZuosoprz48p92pYP8OdQc70H5hZt53hoYNgYFSd+MU6H1 + hJCaXTsiP4IUmBjiwzSp3o1ctP8lWvnyJpAadYdDhaDaAAoaMjCo9cm5OMwc8t8x + hwAPXV2cgWH8fPcT9NLAcwWk + -----END PRIVATE KEY----- + tags: + - after # Only this changes between initial and updated config. + snis: + - name: example.com diff --git a/tests/integration/testdata/sync/024-consumers-with-custom_id-and-username/kong3x.yaml b/tests/integration/testdata/sync/024-consumers-with-custom_id-and-username/kong3x.yaml new file mode 100644 index 000000000..c858f03d1 --- /dev/null +++ b/tests/integration/testdata/sync/024-consumers-with-custom_id-and-username/kong3x.yaml @@ -0,0 +1,12 @@ +_format_version: "3.0" +consumers: +- custom_id: foo + id: ce49186d-7670-445d-a218-897631b29ada + username: Foo +- custom_id: bar + id: 7820f383-7b77-4fcc-af7f-14ff3e256693 + username: foo +- custom_id: custom_id_only + id: 18c62c3c-12cc-429a-8e5a-57f2c3691a6b +- id: 8ef278c9-48c1-43e1-b665-e9bc18fab4c8 + username: username_only \ No newline at end of file diff --git a/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml b/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml new file mode 100644 index 000000000..ca22940db --- /dev/null +++ b/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/kong3x.yaml @@ -0,0 +1,79 @@ +_format_version: "3.0" +services: +- connect_timeout: 60000 + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + routes: + - name: r1 + id: 87b6a97e-f3f7-4c47-857a-7464cb9e202b + https_redirect_status_code: 301 + paths: + - /r1 + +consumer_groups: +- id: 5bcbd3a7-030b-4310-bd1d-2721ff85d236 + name: silver + consumers: + - username: bar + - username: baz + plugins: + - name: rate-limiting-advanced + config: + namespace: silver + limit: + - 7 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 +- id: 77e6691d-67c0-446a-9401-27be2b141aae + name: gold + consumers: + - username: foo + plugins: + - name: rate-limiting-advanced + config: + namespace: gold + limit: + - 10 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 +consumers: +- username: foo + keyauth_credentials: + - key: i-am-special + groups: + - name: gold +- username: bar + keyauth_credentials: + - key: i-am-not-so-special + groups: + - name: silver +- username: baz + keyauth_credentials: + - key: i-am-just-average +plugins: +- name: key-auth + enabled: true + protocols: + - http + - https +- name: rate-limiting-advanced + config: + namespace: silver + limit: + - 5 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 diff --git a/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/konnect.yaml b/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/konnect.yaml new file mode 100644 index 000000000..ca22940db --- /dev/null +++ b/tests/integration/testdata/sync/025-consumer-groups-scoped-plugins/konnect.yaml @@ -0,0 +1,79 @@ +_format_version: "3.0" +services: +- connect_timeout: 60000 + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: mockbin.org + name: svc1 + port: 80 + protocol: http + read_timeout: 60000 + retries: 5 + routes: + - name: r1 + id: 87b6a97e-f3f7-4c47-857a-7464cb9e202b + https_redirect_status_code: 301 + paths: + - /r1 + +consumer_groups: +- id: 5bcbd3a7-030b-4310-bd1d-2721ff85d236 + name: silver + consumers: + - username: bar + - username: baz + plugins: + - name: rate-limiting-advanced + config: + namespace: silver + limit: + - 7 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 +- id: 77e6691d-67c0-446a-9401-27be2b141aae + name: gold + consumers: + - username: foo + plugins: + - name: rate-limiting-advanced + config: + namespace: gold + limit: + - 10 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 +consumers: +- username: foo + keyauth_credentials: + - key: i-am-special + groups: + - name: gold +- username: bar + keyauth_credentials: + - key: i-am-not-so-special + groups: + - name: silver +- username: baz + keyauth_credentials: + - key: i-am-just-average +plugins: +- name: key-auth + enabled: true + protocols: + - http + - https +- name: rate-limiting-advanced + config: + namespace: silver + limit: + - 5 + retry_after_jitter_max: 1 + window_size: + - 60 + window_type: sliding + sync_rate: -1 diff --git a/types/certificate.go b/types/certificate.go index 5349c7ad9..83811681b 100644 --- a/types/certificate.go +++ b/types/certificate.go @@ -12,7 +12,8 @@ import ( // certificateCRUD implements crud.Actions interface. type certificateCRUD struct { - client *kong.Client + client *kong.Client + isKonnect bool } func certificateFromStruct(arg crud.Event) *state.Certificate { @@ -30,6 +31,9 @@ func certificateFromStruct(arg crud.Event) *state.Certificate { func (s *certificateCRUD) Create(ctx context.Context, arg ...crud.Arg) (crud.Arg, error) { event := crud.EventFromArg(arg[0]) certificate := certificateFromStruct(event) + if s.isKonnect { + certificate.SNIs = nil + } createdCertificate, err := s.client.Certificates.Create(ctx, &certificate.Certificate) if err != nil { return nil, err @@ -59,6 +63,9 @@ func (s *certificateCRUD) Update(ctx context.Context, arg ...crud.Arg) (crud.Arg event := crud.EventFromArg(arg[0]) certificate := certificateFromStruct(event) + if s.isKonnect { + certificate.SNIs = nil + } updatedCertificate, err := s.client.Certificates.Create(ctx, &certificate.Certificate) if err != nil { return nil, err @@ -70,6 +77,8 @@ type certificateDiffer struct { kind crud.Kind currentState, targetState *state.KongState + + isKonnect bool } func (d *certificateDiffer) Deletes(handler func(crud.Event) error) error { @@ -138,6 +147,13 @@ func (d *certificateDiffer) createUpdateCertificate( certificateCopy := &state.Certificate{Certificate: *certificate.DeepCopy()} currentCertificate, err := d.currentState.Certificates.Get(*certificate.ID) + if d.isKonnect { + certificateCopy.SNIs = nil + if currentCertificate != nil { + currentCertificate.SNIs = nil + } + } + if errors.Is(err, state.ErrNotFound) { // certificate not present, create it return &crud.Event{ @@ -161,18 +177,20 @@ func (d *certificateDiffer) createUpdateCertificate( // To work around this issues, we set SNIs on certificates here using the // current certificate's SNI list. If there are changes to the SNIs, // subsequent actions on the SNI objects will handle those. - currentSNIs, err := d.currentState.SNIs.GetAllByCertID(*currentCertificate.ID) - if err != nil { - return nil, fmt.Errorf("error looking up current certificate SNIs %q: %w", - certificate.FriendlyName(), err) - } - sniNames := make([]*string, 0) - for _, s := range currentSNIs { - sniNames = append(sniNames, s.Name) - } + if !d.isKonnect { + currentSNIs, err := d.currentState.SNIs.GetAllByCertID(*currentCertificate.ID) + if err != nil { + return nil, fmt.Errorf("error looking up current certificate SNIs %q: %w", + certificate.FriendlyName(), err) + } + sniNames := make([]*string, 0) + for _, s := range currentSNIs { + sniNames = append(sniNames, s.Name) + } - certificateCopy.SNIs = sniNames - currentCertificate.SNIs = sniNames + certificateCopy.SNIs = sniNames + currentCertificate.SNIs = sniNames + } return &crud.Event{ Op: crud.Update, Kind: d.kind, diff --git a/types/consumer.go b/types/consumer.go index 65a1092d2..4d5b52c1c 100644 --- a/types/consumer.go +++ b/types/consumer.go @@ -95,7 +95,7 @@ func (d *consumerDiffer) Deletes(handler func(crud.Event) error) error { } func (d *consumerDiffer) deleteConsumer(consumer *state.Consumer) (*crud.Event, error) { - _, err := d.targetState.Consumers.Get(*consumer.ID) + _, err := d.targetState.Consumers.GetByIDOrUsername(*consumer.ID) if errors.Is(err, state.ErrNotFound) { return &crud.Event{ Op: crud.Delete, @@ -133,7 +133,7 @@ func (d *consumerDiffer) CreateAndUpdates(handler func(crud.Event) error) error func (d *consumerDiffer) createUpdateConsumer(consumer *state.Consumer) (*crud.Event, error) { consumerCopy := &state.Consumer{Consumer: *consumer.DeepCopy()} - currentConsumer, err := d.currentState.Consumers.Get(*consumer.ID) + currentConsumer, err := d.currentState.Consumers.GetByIDOrUsername(*consumer.ID) if errors.Is(err, state.ErrNotFound) { // consumer not present, create it @@ -181,12 +181,38 @@ func (d *consumerDiffer) DuplicatesDeletes() ([]crud.Event, error) { } func (d *consumerDiffer) deleteDuplicateConsumer(targetConsumer *state.Consumer) (*crud.Event, error) { - currentConsumer, err := d.currentState.Consumers.Get(*targetConsumer.Username) - if errors.Is(err, state.ErrNotFound) { - return nil, nil + var ( + idOrUsername string + + currentConsumer *state.Consumer + err error + ) + + if targetConsumer.Username != nil { + idOrUsername = *targetConsumer.Username + } else if targetConsumer.ID != nil { + idOrUsername = *targetConsumer.ID + } + + if idOrUsername != "" { + currentConsumer, err = d.currentState.Consumers.GetByIDOrUsername(idOrUsername) + } + if errors.Is(err, state.ErrNotFound) || idOrUsername == "" { + if targetConsumer.CustomID != nil { + currentConsumer, err = d.currentState.Consumers.GetByCustomID(*targetConsumer.CustomID) + if errors.Is(err, state.ErrNotFound) { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("error looking up consumer by custom_id %q: %w", + *targetConsumer.Username, err) + } + } else { + return nil, nil + } } if err != nil { - return nil, fmt.Errorf("error looking up consumer %q: %w", + return nil, fmt.Errorf("error looking up consumer by username or id %q: %w", *targetConsumer.Username, err) } diff --git a/types/core.go b/types/core.go index b914673bd..9d27a2283 100644 --- a/types/core.go +++ b/types/core.go @@ -336,7 +336,8 @@ func NewEntity(t EntityType, opts EntityOpts) (Entity, error) { return entityImpl{ typ: Certificate, crudActions: &certificateCRUD{ - client: opts.KongClient, + client: opts.KongClient, + isKonnect: opts.IsKonnect, }, postProcessActions: &certificatePostAction{ currentState: opts.CurrentState, @@ -345,6 +346,7 @@ func NewEntity(t EntityType, opts EntityOpts) (Entity, error) { kind: entityTypeToKind(Certificate), currentState: opts.CurrentState, targetState: opts.TargetState, + isKonnect: opts.IsKonnect, }, }, nil case CACertificate: diff --git a/types/plugin.go b/types/plugin.go index 44339c0f2..fc3a59525 100644 --- a/types/plugin.go +++ b/types/plugin.go @@ -26,6 +26,9 @@ func stripPluginReferencesName(plugin *state.Plugin) { if plugin.Plugin.Consumer != nil && plugin.Plugin.Consumer.Username != nil { plugin.Plugin.Consumer.Username = nil } + if plugin.Plugin.ConsumerGroup != nil && plugin.Plugin.ConsumerGroup.Name != nil { + plugin.Plugin.ConsumerGroup.Name = nil + } } func pluginFromStruct(arg crud.Event) *state.Plugin { @@ -111,9 +114,10 @@ func (d *pluginDiffer) Deletes(handler func(crud.Event) error) error { func (d *pluginDiffer) deletePlugin(plugin *state.Plugin) (*crud.Event, error) { plugin = &state.Plugin{Plugin: *plugin.DeepCopy()} name := *plugin.Name - serviceID, routeID, consumerID := foreignNames(plugin) - _, err := d.targetState.Plugins.GetByProp(name, serviceID, routeID, - consumerID) + serviceID, routeID, consumerID, consumerGroupID := foreignNames(plugin) + _, err := d.targetState.Plugins.GetByProp( + name, serviceID, routeID, consumerID, consumerGroupID, + ) if errors.Is(err, state.ErrNotFound) { return &crud.Event{ Op: crud.Delete, @@ -151,9 +155,10 @@ func (d *pluginDiffer) CreateAndUpdates(handler func(crud.Event) error) error { func (d *pluginDiffer) createUpdatePlugin(plugin *state.Plugin) (*crud.Event, error) { plugin = &state.Plugin{Plugin: *plugin.DeepCopy()} name := *plugin.Name - serviceID, routeID, consumerID := foreignNames(plugin) - currentPlugin, err := d.currentState.Plugins.GetByProp(name, - serviceID, routeID, consumerID) + serviceID, routeID, consumerID, consumerGroupID := foreignNames(plugin) + currentPlugin, err := d.currentState.Plugins.GetByProp( + name, serviceID, routeID, consumerID, consumerGroupID, + ) if errors.Is(err, state.ErrNotFound) { // plugin not present, create it @@ -181,7 +186,7 @@ func (d *pluginDiffer) createUpdatePlugin(plugin *state.Plugin) (*crud.Event, er return nil, nil } -func foreignNames(p *state.Plugin) (serviceID, routeID, consumerID string) { +func foreignNames(p *state.Plugin) (serviceID, routeID, consumerID, consumerGroupID string) { if p == nil { return } @@ -194,5 +199,8 @@ func foreignNames(p *state.Plugin) (serviceID, routeID, consumerID string) { if p.Consumer != nil && p.Consumer.ID != nil { consumerID = *p.Consumer.ID } + if p.ConsumerGroup != nil && p.ConsumerGroup.ID != nil { + consumerGroupID = *p.ConsumerGroup.ID + } return } diff --git a/utils/constants.go b/utils/constants.go index da25209bd..0176b6a55 100644 --- a/utils/constants.go +++ b/utils/constants.go @@ -21,7 +21,7 @@ var ( routeDefaults = kong.Route{ PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), - StripPath: kong.Bool(false), + StripPath: kong.Bool(true), Protocols: kong.StringSlice("http", "https"), } targetDefaults = kong.Target{ diff --git a/utils/defaulter_test.go b/utils/defaulter_test.go index c138d204f..9e25f9e62 100644 --- a/utils/defaulter_test.go +++ b/utils/defaulter_test.go @@ -180,7 +180,7 @@ func TestRouteSetTest(t *testing.T) { want: &kong.Route{ PreserveHost: kong.Bool(true), RegexPriority: kong.Int(0), - StripPath: kong.Bool(false), + StripPath: kong.Bool(true), Protocols: kong.StringSlice("http", "https"), }, }, @@ -192,7 +192,7 @@ func TestRouteSetTest(t *testing.T) { want: &kong.Route{ PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), - StripPath: kong.Bool(false), + StripPath: kong.Bool(true), Protocols: kong.StringSlice("http", "tls"), }, }, @@ -202,7 +202,7 @@ func TestRouteSetTest(t *testing.T) { Name: kong.String("foo"), Hosts: kong.StringSlice("1.example.com", "2.example.com"), Methods: kong.StringSlice("GET", "POST"), - StripPath: kong.Bool(false), + StripPath: kong.Bool(true), }, want: &kong.Route{ Name: kong.String("foo"), @@ -210,7 +210,7 @@ func TestRouteSetTest(t *testing.T) { Methods: kong.StringSlice("GET", "POST"), PreserveHost: kong.Bool(false), RegexPriority: kong.Int(0), - StripPath: kong.Bool(false), + StripPath: kong.Bool(true), Protocols: kong.StringSlice("http", "https"), }, }, diff --git a/utils/types.go b/utils/types.go index a4d377df1..8c5c32d70 100644 --- a/utils/types.go +++ b/utils/types.go @@ -81,6 +81,15 @@ func (e ErrArray) Error() string { return res } +func (e ErrArray) ErrorList() []string { + errList := []string{} + + for _, err := range e.Errors { + errList = append(errList, err.Error()) + } + return errList +} + // KongClientConfig holds config details to use to talk to a Kong server. type KongClientConfig struct { Address string @@ -292,6 +301,7 @@ func GetKonnectClient(httpClient *http.Client, config KonnectConfig) (*konnect.C if httpClient == nil { defaultTransport := http.DefaultTransport.(*http.Transport) + defaultTransport.Proxy = http.ProxyFromEnvironment httpClient = http.DefaultClient httpClient.Transport = defaultTransport } @@ -329,6 +339,7 @@ func HTTPClient() *http.Client { Timeout: clientTimeout, }).DialContext, TLSHandshakeTimeout: clientTimeout, + Proxy: http.ProxyFromEnvironment, }, } } diff --git a/utils/utils.go b/utils/utils.go index e00f37c6b..f87d034f3 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -2,6 +2,7 @@ package utils import ( "context" + "errors" "fmt" "net/url" "os" @@ -21,6 +22,14 @@ var ( Kong140Version = semver.MustParse("1.4.0") Kong300Version = semver.MustParse("3.0.0") + Kong340Version = semver.MustParse("3.4.0") +) + +var ErrorConsumerGroupUpgrade = errors.New( + "a rate-limiting-advanced plugin with config.consumer_groups\n" + + "and/or config.enforce_consumer_groups was found. Please use Consumer Groups scoped\n" + + "Plugins when running against Kong Enterprise 3.4.0 and above.\n\n" + + "Check https://docs.konghq.com/gateway/latest/kong-enterprise/consumer-groups/ for more information", ) var UpgradeMessage = "Please upgrade your configuration to account for 3.0\n" + @@ -148,6 +157,16 @@ func GetConsumerReference(c kong.Consumer) *kong.Consumer { return consumer } +// GetConsumerGroupReference returns a name+ID only copy of the input consumer-group, +// for use in references from other objects +func GetConsumerGroupReference(c kong.ConsumerGroup) *kong.ConsumerGroup { + consumerGroup := &kong.ConsumerGroup{ID: kong.String(*c.ID)} + if c.Name != nil { + consumerGroup.Name = kong.String(*c.Name) + } + return consumerGroup +} + // GetServiceReference returns a name+ID only copy of the input service, // for use in references from other objects func GetServiceReference(s kong.Service) *kong.Service {