diff --git a/.ci/clean_kong.sh b/.ci/clean_kong.sh new file mode 100755 index 000000000..f8e3ee13a --- /dev/null +++ b/.ci/clean_kong.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -euo pipefail + +source ./.ci/lib.sh + +cleanup() { + local -r resource=${1?resource type required} + shift + + docker "$resource" ls "$@" \ + --filter "label=$DOCKER_LABEL" \ + --quiet \ + | while read -r id; do + docker "$resource" rm \ + --force \ + "$id" + done +} + +cleanup container --all +cleanup volume +cleanup network diff --git a/.ci/lib.sh b/.ci/lib.sh new file mode 100644 index 000000000..077441ca9 --- /dev/null +++ b/.ci/lib.sh @@ -0,0 +1,86 @@ +set -euo pipefail + +readonly NETWORK_NAME=deck-test +readonly DOCKER_LABEL=com.konghq.test.deck=1 +readonly KONG_PG_HOST=kong-postgres +readonly KONG_PG_USER=kong +readonly KONG_PG_DATABASE=kong +readonly KONG_PG_PASSWORD=kong +readonly KONG_PG_PORT=5432 + +readonly DOCKER_ARGS=( + --label "$DOCKER_LABEL" + --network "$NETWORK_NAME" + --volume "$PWD/tests/integration/testdata/filters:/filters:ro" + -e "KONG_DATABASE=postgres" + -e "KONG_PG_HOST=$KONG_PG_HOST" + -e "KONG_PG_PORT=$KONG_PG_PORT" + -e "KONG_PG_USER=$KONG_PG_USER" + -e "KONG_PG_DATABASE=$KONG_PG_DATABASE" + -e "KONG_PG_PASSWORD=$KONG_PG_PASSWORD" + -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" + -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" + -e "KONG_PROXY_ERROR_LOG=/dev/stderr" + -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" + -e "KONG_LOG_LEVEL=${KONG_LOG_LEVEL:-notice}" + -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" + -e "KONG_WASM_FILTERS_PATH=/filters" + -e "KONG_WASM=on" +) + +waitContainer() { + local -r container=$1 + shift + + for _ in {1..100}; do + echo "waiting for $container" + if docker exec \ + --user root \ + "$container" \ + "$@" + then + return + fi + sleep 0.2 + done + + echo "FATAL: failed waiting for $container" + exit 1 +} + +initNetwork() { + docker network create \ + --label "$DOCKER_LABEL" \ + "$NETWORK_NAME" +} + +initDb() { + docker run \ + --rm \ + --detach \ + --name "$KONG_PG_HOST" \ + --label "$DOCKER_LABEL" \ + --network $NETWORK_NAME \ + -p "${KONG_PG_PORT}:${KONG_PG_PORT}" \ + -e "POSTGRES_USER=$KONG_PG_USER" \ + -e "POSTGRES_DB=$KONG_PG_DATABASE" \ + -e "POSTGRES_PASSWORD=$KONG_PG_PASSWORD" \ + postgres:9.6 + + waitContainer "$KONG_PG_HOST" pg_isready +} + +initMigrations() { + local -r image=$1 + shift + + docker run \ + --rm \ + "${DOCKER_ARGS[@]}" \ + "$@" \ + "$image" \ + kong migrations bootstrap \ + --yes \ + --force \ + --db-timeout 30 +} diff --git a/.ci/setup_kong.sh b/.ci/setup_kong.sh index ecffc4773..aa8319558 100755 --- a/.ci/setup_kong.sh +++ b/.ci/setup_kong.sh @@ -1,65 +1,26 @@ #!/bin/bash -set -e +set -euo pipefail -KONG_IMAGE=${KONG_IMAGE:-kong} -NETWORK_NAME=deck-test +source ./.ci/lib.sh -PG_CONTAINER_NAME=pg -DATABASE_USER=kong -DATABASE_NAME=kong -KONG_DB_PASSWORD=kong -KONG_PG_HOST=pg +readonly KONG_IMAGE=${KONG_IMAGE:-kong} +readonly GATEWAY_CONTAINER_NAME=kong -GATEWAY_CONTAINER_NAME=kong - -waitContainer() { - for try in {1..100}; do - echo "waiting for $1" - docker exec --user root $2 $3 && break; - sleep 0.2 - done -} - -# create docker network -docker network create $NETWORK_NAME - -# Start a PostgreSQL container -docker run --rm -d --name $PG_CONTAINER_NAME \ - --network=$NETWORK_NAME \ - -p 5432:5432 \ - -e "POSTGRES_USER=$DATABASE_USER" \ - -e "POSTGRES_DB=$DATABASE_NAME" \ - -e "POSTGRES_PASSWORD=$KONG_DB_PASSWORD" \ - postgres:9.6 - -waitContainer "PostGres" $PG_CONTAINER_NAME pg_isready - -# Prepare the Kong database -docker run --rm --network=$NETWORK_NAME \ - -e "KONG_DATABASE=postgres" \ - -e "KONG_PG_HOST=$KONG_PG_HOST" \ - -e "KONG_PG_PASSWORD=$KONG_DB_PASSWORD" \ - -e "KONG_PASSWORD=$KONG_DB_PASSWORD" \ - $KONG_IMAGE kong migrations bootstrap +initNetwork +initDb +initMigrations "$KONG_IMAGE" # Start Kong Gateway -docker run -d --name $GATEWAY_CONTAINER_NAME \ - --network=$NETWORK_NAME \ - -e "KONG_DATABASE=postgres" \ - -e "KONG_PG_HOST=$KONG_PG_HOST" \ - -e "KONG_PG_USER=$DATABASE_USER" \ - -e "KONG_PG_PASSWORD=$KONG_DB_PASSWORD" \ - -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \ - -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \ - -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \ - -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \ - -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \ - -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \ - -p 8000:8000 \ - -p 8443:8443 \ - -p 127.0.0.1:8001:8001 \ - -p 127.0.0.1:8444:8444 \ - $KONG_IMAGE - -waitContainer "Kong" $GATEWAY_CONTAINER_NAME "kong health" +docker run \ + --detach \ + --name "$GATEWAY_CONTAINER_NAME" \ + "${DOCKER_ARGS[@]}" \ + -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \ + -p 8000:8000 \ + -p 8443:8443 \ + -p 127.0.0.1:8001:8001 \ + -p 127.0.0.1:8444:8444 \ + "$KONG_IMAGE" + +waitContainer "$GATEWAY_CONTAINER_NAME" kong health diff --git a/.ci/setup_kong_ee.sh b/.ci/setup_kong_ee.sh index 4ee6d5799..41810122d 100755 --- a/.ci/setup_kong_ee.sh +++ b/.ci/setup_kong_ee.sh @@ -1,6 +1,8 @@ #!/bin/bash -set -e +set -euo pipefail + +source ./.ci/lib.sh MY_SECRET_CERT='''-----BEGIN CERTIFICATE----- MIIEczCCAlugAwIBAgIJAMw8/GAiHIFBMA0GCSqGSIb3DQEBCwUAMDYxCzAJBgNV @@ -57,73 +59,33 @@ KlBs7O9y+fc4AIIn6JD+9tymB1TWEn1B+3Vv6jmtzbztuCQTbJ6rTT3CFcE6TdyJ 8rYuG3/p2VkcG29TWbQARtj5ewv9p5QNfaecUzN+tps89YzawWQBanwI -----END RSA PRIVATE KEY-----''' -KONG_IMAGE=${KONG_IMAGE:-kong/kong-gateway} -NETWORK_NAME=deck-test - -PG_CONTAINER_NAME=pg -DATABASE_USER=kong -DATABASE_NAME=kong -KONG_DB_PASSWORD=kong -KONG_PG_HOST=pg - -GATEWAY_CONTAINER_NAME=kong - -waitContainer() { - for try in {1..100}; do - echo "waiting for $1" - docker exec --user root $2 $3 && break; - sleep 0.2 - done -} - -# create docker network -docker network create $NETWORK_NAME - -# Start a PostgreSQL container -docker run --rm -d --name $PG_CONTAINER_NAME \ - --network=$NETWORK_NAME \ - -p 5432:5432 \ - -e "POSTGRES_USER=$DATABASE_USER" \ - -e "POSTGRES_DB=$DATABASE_NAME" \ - -e "POSTGRES_PASSWORD=$KONG_DB_PASSWORD" \ - postgres:9.6 - -waitContainer "PostGres" $PG_CONTAINER_NAME pg_isready +readonly KONG_IMAGE=${KONG_IMAGE:-kong/kong-gateway} +readonly GATEWAY_CONTAINER_NAME=kong -# Prepare the Kong database -docker run --rm --network=$NETWORK_NAME \ - -e "KONG_DATABASE=postgres" \ - -e "KONG_PG_HOST=$KONG_PG_HOST" \ - -e "KONG_PG_PASSWORD=$KONG_DB_PASSWORD" \ - -e "KONG_PASSWORD=$KONG_DB_PASSWORD" \ - -e "KONG_LICENSE_DATA=$KONG_LICENSE_DATA" \ - $KONG_IMAGE kong migrations bootstrap +initNetwork +initDb +initMigrations "${KONG_IMAGE}" \ + -e "KONG_LICENSE_DATA=${KONG_LICENSE_DATA}" # Start Kong Gateway EE -docker run -d --name $GATEWAY_CONTAINER_NAME \ - --network=$NETWORK_NAME \ - -e "KONG_DATABASE=postgres" \ - -e "KONG_PG_HOST=$KONG_PG_HOST" \ - -e "KONG_PG_USER=$DATABASE_USER" \ - -e "KONG_PG_PASSWORD=$KONG_DB_PASSWORD" \ - -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \ - -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \ - -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \ - -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \ - -e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \ - -e "KONG_PORTAL_GUI_URI=127.0.0.1:8003" \ - -e "KONG_ADMIN_GUI_URL=http://127.0.0.1:8002" \ - -e "KONG_LICENSE_DATA=$KONG_LICENSE_DATA" \ - -e "MY_SECRET_CERT=$MY_SECRET_CERT" \ - -e "MY_SECRET_KEY=$MY_SECRET_KEY" \ - -p 8000:8000 \ - -p 8443:8443 \ - -p 8001:8001 \ - -p 8444:8444 \ - -p 8002:8002 \ - -p 8445:8445 \ - -p 8003:8003 \ - -p 8004:8004 \ - $KONG_IMAGE +docker run \ + --detach \ + --name $GATEWAY_CONTAINER_NAME \ + "${DOCKER_ARGS[@]}" \ + -e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \ + -e "KONG_PORTAL_GUI_URI=127.0.0.1:8003" \ + -e "KONG_ADMIN_GUI_URL=http://127.0.0.1:8002" \ + -e "KONG_LICENSE_DATA=$KONG_LICENSE_DATA" \ + -e "MY_SECRET_CERT=$MY_SECRET_CERT" \ + -e "MY_SECRET_KEY=$MY_SECRET_KEY" \ + -p 8000:8000 \ + -p 8443:8443 \ + -p 8001:8001 \ + -p 8444:8444 \ + -p 8002:8002 \ + -p 8445:8445 \ + -p 8003:8003 \ + -p 8004:8004 \ + "$KONG_IMAGE" -waitContainer "Kong" $GATEWAY_CONTAINER_NAME "kong health" +waitContainer "${GATEWAY_CONTAINER_NAME}" kong health diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 426459168..12a2c5094 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -36,7 +36,7 @@ jobs: - 'kong:3.4' - 'kong:3.5' - 'kong:3.6' - - 'kong/kong:master-alpine' + - 'kong/kong:master' env: KONG_ANONYMOUS_REPORTS: "off" KONG_IMAGE: ${{ matrix.kong_image }} diff --git a/Makefile b/Makefile index a23318934..c4c6bf028 100644 --- a/Makefile +++ b/Makefile @@ -40,3 +40,7 @@ test-integration: go test -v -count=1 -tags=integration \ -race \ ./tests/integration/... + +.PHONY: clean +clean: + bash .ci/clean_kong.sh diff --git a/cmd/common.go b/cmd/common.go index c7ebc79df..60988ed1c 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -528,6 +528,7 @@ func containsProxyConfiguration(content reconcilerUtils.KongRawState) bool { return len(content.Services) != 0 || len(content.Routes) != 0 || len(content.Plugins) != 0 || + len(content.FilterChains) != 0 || len(content.Upstreams) != 0 || len(content.Certificates) != 0 || len(content.CACertificates) != 0 || diff --git a/cmd/gateway_validate.go b/cmd/gateway_validate.go index f0791f9c5..558bbb9f7 100644 --- a/cmd/gateway_validate.go +++ b/cmd/gateway_validate.go @@ -328,5 +328,8 @@ func ensureGetAllMethods() error { if _, err := reconcilerUtils.CallGetAll(dummyEmptyState.RBACRoles); err != nil { return err } + if _, err := reconcilerUtils.CallGetAll(dummyEmptyState.FilterChains); err != nil { + return err + } return nil } diff --git a/go.mod b/go.mod index 13435f623..dd4a3289c 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/fatih/color v1.15.0 github.com/google/go-cmp v0.6.0 github.com/kong/go-apiops v0.1.33 - github.com/kong/go-database-reconciler v1.12.1 + github.com/kong/go-database-reconciler v1.12.2 github.com/kong/go-kong v0.55.0 github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v1.8.0 @@ -40,7 +40,7 @@ require ( github.com/pb33f/doctor v0.0.6 // indirect github.com/pb33f/libopenapi v0.16.1 // indirect github.com/pb33f/libopenapi-validator v0.0.49 // indirect - github.com/shirou/gopsutil/v3 v3.24.4 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/ssgelm/cookiejarparser v1.0.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect @@ -131,8 +131,8 @@ require ( golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/term v0.20.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/term v0.21.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/tools v0.21.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/go.sum b/go.sum index 75251f7ec..218cfbff0 100644 --- a/go.sum +++ b/go.sum @@ -192,6 +192,8 @@ github.com/kong/go-apiops v0.1.33 h1:Y7IVksHPdHcXM6C+gPc25JiY4KRgYDAOn/jTx3sDU1k github.com/kong/go-apiops v0.1.33/go.mod h1:o8lzBtbCLSXCMKzzqR8dcBhB7yzPs+9csAMZ1T1hsL0= github.com/kong/go-database-reconciler v1.12.1 h1:v6CjU2eo9gAnw+CNQg6r+lW66vuU34XddnVZhd91MdU= github.com/kong/go-database-reconciler v1.12.1/go.mod h1:fX9SV2ukbuUdVw2h1rakWTi/DUxAV9cbj4QWttoiRrc= +github.com/kong/go-database-reconciler v1.12.2 h1:bnlvLCgP4OjgJOK5JYqq1MlIJ2F7erXERMj8n/LNU8M= +github.com/kong/go-database-reconciler v1.12.2/go.mod h1:bUPJkoeW//x4hzNxewQMoIkeoDzJzunI0stDMYJ3BkU= github.com/kong/go-kong v0.55.0 h1:lonKRzsDGk12dh9E+y+pWnY2ThXhKuMHjzBHSpCvQLw= github.com/kong/go-kong v0.55.0/go.mod h1:i1cMgTu6RYPHSyMpviShddRnc+DML/vlpgKC00hr8kU= github.com/kong/go-slugify v1.0.0 h1:vCFAyf2sdoSlBtLcrmDWUFn0ohlpKiKvQfXZkO5vSKY= @@ -301,6 +303,8 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= 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= @@ -440,6 +444,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -447,6 +453,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/tests/integration/dump_test.go b/tests/integration/dump_test.go index 8556da046..ac2c0c924 100644 --- a/tests/integration/dump_test.go +++ b/tests/integration/dump_test.go @@ -284,3 +284,41 @@ func Test_Dump_ConsumerGroupConsumersWithCustomID_Konnect(t *testing.T) { assert.NoError(t, err) assert.Equal(t, expected, output) } + +func Test_Dump_FilterChains(t *testing.T) { + runWhen(t, "kong", ">=3.4.0") + setup(t) + + tests := []struct { + version string + input string + expected string + }{ + { + version: "<3.5.0", + input: "testdata/dump/004-filter-chains/kong-3.4.x.yaml", + expected: "testdata/dump/004-filter-chains/expected-3.4.x.yaml", + }, + { + version: ">=3.5.0", + input: "testdata/dump/004-filter-chains/kong.yaml", + expected: "testdata/dump/004-filter-chains/expected.yaml", + }, + } + + for _, tc := range tests { + t.Run(tc.version, func(t *testing.T) { + runWhen(t, "kong", tc.version) + require.NoError(t, sync(tc.input)) + + var output string + flags := []string{"-o", "-"} + output, err := dump(flags...) + assert.NoError(t, err) + + expected, err := readFile(tc.expected) + assert.NoError(t, err) + assert.Equal(t, expected, output) + }) + } +} diff --git a/tests/integration/sync_test.go b/tests/integration/sync_test.go index 1d194cb08..bb79ade0d 100644 --- a/tests/integration/sync_test.go +++ b/tests/integration/sync_test.go @@ -5212,3 +5212,174 @@ func Test_Sync_ConsumerGroupConsumerWithTags(t *testing.T) { require.NoError(t, sync("testdata/sync/032-consumer-group-consumers-with-tags/initial.yaml")) testKongState(t, client, false, expectedState, nil) } + +func Test_Sync_FilterChains(t *testing.T) { + runWhen(t, "kong", ">=3.4.0") + setup(t) + + client, err := getTestClient() + if err != nil { + t.Fatalf(err.Error()) + } + + service := kong.Service{ + ID: kong.String("58076db2-28b6-423b-ba39-a797193017f7"), + Name: kong.String("test"), + ConnectTimeout: kong.Int(60000), + Enabled: kong.Bool(true), + Host: kong.String("test"), + Port: kong.Int(8080), + Protocol: kong.String("http"), + ReadTimeout: kong.Int(60000), + Retries: kong.Int(5), + WriteTimeout: kong.Int(60000), + } + + route := kong.Route{ + ID: kong.String("37fc74bd-bac6-4bce-bd54-6ec4d341c1c1"), + Name: kong.String("r1"), + Paths: kong.StringSlice("/r1"), + PathHandling: kong.String("v0"), + PreserveHost: kong.Bool(false), + Protocols: kong.StringSlice("http", "https"), + RegexPriority: new(int), + StripPath: kong.Bool(true), + HTTPSRedirectStatusCode: kong.Int(426), + RequestBuffering: kong.Bool(true), + ResponseBuffering: kong.Bool(true), + Service: &kong.Service{ + ID: service.ID, + }, + } + + serviceChain := kong.FilterChain{ + Name: kong.String("service"), + ID: kong.String("d3ed9313-acf2-4982-af3c-83d9b52795c3"), + Enabled: kong.Bool(true), + Service: &kong.Service{ + ID: service.ID, + }, + Filters: []*kong.Filter{ + { + Name: kong.String("response_transformer"), + Enabled: kong.Bool(true), + Config: kong.JSONRawMessage(`"{\n \"add\": {\n \"headers\": [\n \"x-service:test\"\n ]\n }\n}\n"`), + }, + }, + } + + routeChain := kong.FilterChain{ + Name: kong.String("route"), + ID: kong.String("7b95fe94-df9b-421a-8a7b-c4d8fddbf363"), + Enabled: kong.Bool(true), + Route: &kong.Route{ + ID: route.ID, + }, + Filters: []*kong.Filter{ + { + Name: kong.String("response_transformer"), + Enabled: kong.Bool(true), + Config: kong.JSONRawMessage(`"{\n \"add\": {\n \"headers\": [\n \"x-route:test\"\n ]\n }\n}\n"`), + }, + }, + } + + tests := []struct { + name string + version string + createFile string + createState func(*utils.KongRawState) + updateFile string + updateState func(*utils.KongRawState) + deleteFile string + deleteState func(*utils.KongRawState) + }{ + // in Kong 3.4.x, filter configurations must always be strings + { + name: "kong 3.4.x", + version: "<3.5.0", + createFile: "testdata/sync/033-filter-chains/create-3.4.x.yaml", + createState: func(state *utils.KongRawState) { + state.FilterChains = []*kong.FilterChain{ + routeChain.DeepCopy(), + serviceChain.DeepCopy(), + } + }, + updateFile: "testdata/sync/033-filter-chains/update-3.4.x.yaml", + updateState: func(state *utils.KongRawState) { + state.FilterChains[0].Filters[0].Enabled = kong.Bool(false) + cfg := kong.JSONRawMessage(`"{\n \"add\": {\n \"headers\": [\n \"x-service:CHANGED\"\n ]\n }\n}\n"`) + state.FilterChains[1].Filters[0].Config = cfg + }, + deleteFile: "testdata/sync/033-filter-chains/delete-3.4.x.yaml", + deleteState: func(state *utils.KongRawState) { + state.FilterChains = []*kong.FilterChain{ + state.FilterChains[0], + } + }, + }, + // kong 3.5.0 introduced optional JSON[Schema] support for filter configurations + { + name: "kong >=3.5", + version: ">=3.5.0", + createFile: "testdata/sync/033-filter-chains/create.yaml", + createState: func(state *utils.KongRawState) { + rc := routeChain.DeepCopy() + rc.Filters[0].Config = kong.JSONRawMessage(`{"add":{"headers":["x-route:test"]}}`) + + sc := serviceChain.DeepCopy() + sc.Filters[0].Config = kong.JSONRawMessage(`{"add":{"headers":["x-service:test"]}}`) + + state.FilterChains = []*kong.FilterChain{ + rc, + sc, + } + }, + updateFile: "testdata/sync/033-filter-chains/update.yaml", + updateState: func(state *utils.KongRawState) { + state.FilterChains[0].Filters[0].Enabled = kong.Bool(false) + state.FilterChains[1].Filters[0].Config = kong.JSONRawMessage(`{"add":{"headers":["x-service:CHANGED"]}}`) + }, + deleteFile: "testdata/sync/033-filter-chains/delete.yaml", + deleteState: func(state *utils.KongRawState) { + state.FilterChains = []*kong.FilterChain{ + state.FilterChains[0], + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runWhen(t, "kong", tc.version) + + state := utils.KongRawState{ + Services: []*kong.Service{&service}, + Routes: []*kong.Route{&route}, + } + + require.NoError(t, sync("testdata/sync/033-filter-chains/init.yaml")) + + testKongState(t, client, false, state, nil) + + require.NoError(t, sync(tc.createFile)) + tc.createState(&state) + testKongState(t, client, false, state, nil) + + require.NoError(t, sync(tc.updateFile)) + tc.updateState(&state) + testKongState(t, client, false, state, nil) + + require.NoError(t, sync(tc.deleteFile)) + tc.deleteState(&state) + testKongState(t, client, false, state, nil) + }) + } +} + +func Test_Sync_FilterChainsUnsupported(t *testing.T) { + runWhen(t, "kong", "<3.4.0") + setup(t) + require.NoError(t, sync("testdata/sync/033-filter-chains/init.yaml")) + require.Error(t, sync("testdata/sync/033-filter-chains/create.yaml")) +} diff --git a/tests/integration/test_utils.go b/tests/integration/test_utils.go index dadb086fb..83d5a8677 100644 --- a/tests/integration/test_utils.go +++ b/tests/integration/test_utils.go @@ -226,6 +226,7 @@ func testKongState(t *testing.T, client *kong.Client, isKonnect bool, cmpopts.IgnoreFields(kong.ConsumerGroup{}, "CreatedAt", "ID"), cmpopts.IgnoreFields(kong.ConsumerGroupPlugin{}, "CreatedAt", "ID"), cmpopts.IgnoreFields(kong.KeyAuth{}, "ID", "CreatedAt"), + cmpopts.IgnoreFields(kong.FilterChain{}, "CreatedAt", "UpdatedAt"), cmpopts.SortSlices(sortSlices), cmpopts.SortSlices(func(a, b *string) bool { return *a < *b }), cmpopts.EquateEmpty(), diff --git a/tests/integration/testdata/dump/004-filter-chains/expected-3.4.x.yaml b/tests/integration/testdata/dump/004-filter-chains/expected-3.4.x.yaml new file mode 100644 index 000000000..ddfa735b2 --- /dev/null +++ b/tests/integration/testdata/dump/004-filter-chains/expected-3.4.x.yaml @@ -0,0 +1,53 @@ +_format_version: "3.0" +services: +- connect_timeout: 60000 + enabled: true + filter_chains: + - enabled: true + filters: + - config: | + { + "add": { + "headers": [ + "x-service:test" + ] + } + } + enabled: true + name: response_transformer + name: service + host: test + name: test + port: 8080 + protocol: http + read_timeout: 60000 + retries: 5 + routes: + - filter_chains: + - enabled: true + filters: + - config: | + { + "add": { + "headers": [ + "x-route:test" + ] + } + } + enabled: true + name: response_transformer + name: route + https_redirect_status_code: 426 + name: r1 + path_handling: v0 + paths: + - /r1 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + request_buffering: true + response_buffering: true + strip_path: true + write_timeout: 60000 diff --git a/tests/integration/testdata/dump/004-filter-chains/expected.yaml b/tests/integration/testdata/dump/004-filter-chains/expected.yaml new file mode 100644 index 000000000..911cab006 --- /dev/null +++ b/tests/integration/testdata/dump/004-filter-chains/expected.yaml @@ -0,0 +1,45 @@ +_format_version: "3.0" +services: +- connect_timeout: 60000 + enabled: true + filter_chains: + - enabled: true + filters: + - config: + add: + headers: + - x-service:test + enabled: true + name: response_transformer + name: service + host: test + name: test + port: 8080 + protocol: http + read_timeout: 60000 + retries: 5 + routes: + - filter_chains: + - enabled: true + filters: + - config: + add: + headers: + - x-route:test + enabled: true + name: response_transformer + name: route + https_redirect_status_code: 426 + name: r1 + path_handling: v0 + paths: + - /r1 + preserve_host: false + protocols: + - http + - https + regex_priority: 0 + request_buffering: true + response_buffering: true + strip_path: true + write_timeout: 60000 diff --git a/tests/integration/testdata/dump/004-filter-chains/kong-3.4.x.yaml b/tests/integration/testdata/dump/004-filter-chains/kong-3.4.x.yaml new file mode 100644 index 000000000..9f7e656d3 --- /dev/null +++ b/tests/integration/testdata/dump/004-filter-chains/kong-3.4.x.yaml @@ -0,0 +1,39 @@ +_format_version: "3.0" +services: +- filter_chains: + - name: service + id: d3ed9313-acf2-4982-af3c-83d9b52795c3 + filters: + - config: | + { + "add": { + "headers": [ + "x-service:test" + ] + } + } + name: response_transformer + name: test + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: test + port: 8080 + protocol: http + routes: + - filter_chains: + - filters: + - config: | + { + "add": { + "headers": [ + "x-route:test" + ] + } + } + name: response_transformer + id: 7b95fe94-df9b-421a-8a7b-c4d8fddbf363 + name: route + name: r1 + id: 37fc74bd-bac6-4bce-bd54-6ec4d341c1c1 + paths: + - /r1 + diff --git a/tests/integration/testdata/dump/004-filter-chains/kong.yaml b/tests/integration/testdata/dump/004-filter-chains/kong.yaml new file mode 100644 index 000000000..797e926f7 --- /dev/null +++ b/tests/integration/testdata/dump/004-filter-chains/kong.yaml @@ -0,0 +1,30 @@ +_format_version: "3.0" +services: +- filter_chains: + - name: service + id: d3ed9313-acf2-4982-af3c-83d9b52795c3 + filters: + - name: response_transformer + config: + add: + headers: + - x-service:test + name: test + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: test + port: 8080 + protocol: http + routes: + - name: r1 + id: 37fc74bd-bac6-4bce-bd54-6ec4d341c1c1 + paths: + - /r1 + filter_chains: + - id: 7b95fe94-df9b-421a-8a7b-c4d8fddbf363 + name: route + filters: + - name: response_transformer + config: + add: + headers: + - x-route:test diff --git a/tests/integration/testdata/filters/response_transformer.meta.json b/tests/integration/testdata/filters/response_transformer.meta.json new file mode 100644 index 000000000..eb0bbaeba --- /dev/null +++ b/tests/integration/testdata/filters/response_transformer.meta.json @@ -0,0 +1,138 @@ +{ + "config_schema": { + "$id": "response_transformer", + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Response Transformer", + "type": "object", + "anyOf": [ + {"required": ["add"]}, + {"required": ["remove"]}, + {"required": ["rename"]}, + {"required": ["replace"]}, + {"required": ["append"]} + ], + "additionalProperties": false, + "properties": { + "add": { + "type": "object", + "additionalProperties": false, + "properties": { + "headers": { + "description": "add headers", + "$ref": "#/definitions/stringArray" + }, + "json": { + "description": "add JSON properties", + "$ref": "#/definitions/stringArray" + }, + "json_types": { + "description": "List of JSON type names. Specify the types of the JSON values returned when adding JSON properties.", + "$ref": "#/definitions/JsonTypesArray" + } + }, + "anyOf": [ + {"required": ["headers"]}, + {"required": ["json"]} + ] + }, + "remove": { + "type": "object", + "additionalProperties": false, + "properties": { + "headers": { + "description": "remove headers", + "$ref": "#/definitions/stringArray" + }, + "json": { + "description": "remove JSON attributes", + "$ref": "#/definitions/stringArray" + } + }, + "anyOf": [ + {"required": ["headers"]}, + {"required": ["json"]} + ] + }, + "rename": { + "type": "object", + "additionalProperties": false, + "properties": { + "headers": { + "description": "rename headers", + "$ref": "#/definitions/stringArray" + }, + "json": { + "description": "rename JSON attributes", + "$ref": "#/definitions/stringArray" + } + }, + "anyOf": [ + {"required": ["headers"]}, + {"required": ["json"]} + ] + }, + "replace": { + "type": "object", + "additionalProperties": false, + "properties": { + "headers": { + "description": "replace headers", + "$ref": "#/definitions/stringArray" + }, + "json": { + "description": "replace JSON properties", + "$ref": "#/definitions/stringArray" + }, + "json_types": { + "description": "List of JSON type names. Specify the types of the JSON values returned when replacing JSON properties.", + "$ref": "#/definitions/JsonTypesArray" + } + }, + "anyOf": [ + {"required": ["headers"]}, + {"required": ["json"]} + ] + }, + "append": { + "type": "object", + "additionalProperties": false, + "properties": { + "headers": { + "description": "append headers", + "$ref": "#/definitions/stringArray" + }, + "json": { + "description": "append JSON properties", + "$ref": "#/definitions/stringArray" + }, + "json_types": { + "description": "List of JSON type names. Specify the types of the JSON values returned when appending JSON properties.", + "$ref": "#/definitions/JsonTypesArray" + } + }, + "anyOf": [ + {"required": ["headers"]}, + {"required": ["json"]} + ] + } + }, + "definitions": { + "stringArray": { + "type": "array", + "items": { + "type": "string" + } + }, + "JsonTypesArray": { + "type": "array", + "items": { + "enum": [ + "boolean", + "number", + "string" + ] + } + } + } + } +} diff --git a/tests/integration/testdata/filters/response_transformer.wasm b/tests/integration/testdata/filters/response_transformer.wasm new file mode 100644 index 000000000..c063a340d Binary files /dev/null and b/tests/integration/testdata/filters/response_transformer.wasm differ diff --git a/tests/integration/testdata/sync/033-filter-chains/create-3.4.x.yaml b/tests/integration/testdata/sync/033-filter-chains/create-3.4.x.yaml new file mode 100644 index 000000000..b48dd8099 --- /dev/null +++ b/tests/integration/testdata/sync/033-filter-chains/create-3.4.x.yaml @@ -0,0 +1,38 @@ +_format_version: "3.0" +services: +- name: test + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: test + port: 8080 + protocol: http + filter_chains: + - name: service + id: d3ed9313-acf2-4982-af3c-83d9b52795c3 + filters: + - name: response_transformer + config: | + { + "add": { + "headers": [ + "x-service:test" + ] + } + } + routes: + - name: r1 + id: 37fc74bd-bac6-4bce-bd54-6ec4d341c1c1 + paths: + - /r1 + filter_chains: + - name: route + id: 7b95fe94-df9b-421a-8a7b-c4d8fddbf363 + filters: + - name: response_transformer + config: | + { + "add": { + "headers": [ + "x-route:test" + ] + } + } diff --git a/tests/integration/testdata/sync/033-filter-chains/create.yaml b/tests/integration/testdata/sync/033-filter-chains/create.yaml new file mode 100644 index 000000000..c17227e0e --- /dev/null +++ b/tests/integration/testdata/sync/033-filter-chains/create.yaml @@ -0,0 +1,30 @@ +_format_version: "3.0" +services: +- name: test + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: test + port: 8080 + protocol: http + filter_chains: + - name: service + id: d3ed9313-acf2-4982-af3c-83d9b52795c3 + filters: + - name: response_transformer + config: + add: + headers: + - x-service:test + routes: + - name: r1 + id: 37fc74bd-bac6-4bce-bd54-6ec4d341c1c1 + paths: + - /r1 + filter_chains: + - name: route + id: 7b95fe94-df9b-421a-8a7b-c4d8fddbf363 + filters: + - name: response_transformer + config: + add: + headers: + - x-route:test diff --git a/tests/integration/testdata/sync/033-filter-chains/delete-3.4.x.yaml b/tests/integration/testdata/sync/033-filter-chains/delete-3.4.x.yaml new file mode 100644 index 000000000..975a2920d --- /dev/null +++ b/tests/integration/testdata/sync/033-filter-chains/delete-3.4.x.yaml @@ -0,0 +1,26 @@ +_format_version: "3.0" +services: +- name: test + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: test + port: 8080 + protocol: http + routes: + - name: r1 + id: 37fc74bd-bac6-4bce-bd54-6ec4d341c1c1 + paths: + - /r1 + filter_chains: + - name: route + id: 7b95fe94-df9b-421a-8a7b-c4d8fddbf363 + filters: + - name: response_transformer + enabled: false + config: | + { + "add": { + "headers": [ + "x-route:test" + ] + } + } diff --git a/tests/integration/testdata/sync/033-filter-chains/delete.yaml b/tests/integration/testdata/sync/033-filter-chains/delete.yaml new file mode 100644 index 000000000..b8409860b --- /dev/null +++ b/tests/integration/testdata/sync/033-filter-chains/delete.yaml @@ -0,0 +1,22 @@ +_format_version: "3.0" +services: +- name: test + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: test + port: 8080 + protocol: http + routes: + - name: r1 + id: 37fc74bd-bac6-4bce-bd54-6ec4d341c1c1 + paths: + - /r1 + filter_chains: + - name: route + id: 7b95fe94-df9b-421a-8a7b-c4d8fddbf363 + filters: + - name: response_transformer + enabled: false + config: + add: + headers: + - x-route:test diff --git a/tests/integration/testdata/sync/033-filter-chains/init.yaml b/tests/integration/testdata/sync/033-filter-chains/init.yaml new file mode 100644 index 000000000..d0816335a --- /dev/null +++ b/tests/integration/testdata/sync/033-filter-chains/init.yaml @@ -0,0 +1,12 @@ +_format_version: "3.0" +services: +- name: test + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: test + port: 8080 + protocol: http + routes: + - name: r1 + id: 37fc74bd-bac6-4bce-bd54-6ec4d341c1c1 + paths: + - /r1 diff --git a/tests/integration/testdata/sync/033-filter-chains/update-3.4.x.yaml b/tests/integration/testdata/sync/033-filter-chains/update-3.4.x.yaml new file mode 100644 index 000000000..e114b85bd --- /dev/null +++ b/tests/integration/testdata/sync/033-filter-chains/update-3.4.x.yaml @@ -0,0 +1,39 @@ +_format_version: "3.0" +services: +- name: test + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: test + port: 8080 + protocol: http + filter_chains: + - name: service + id: d3ed9313-acf2-4982-af3c-83d9b52795c3 + filters: + - name: response_transformer + config: | + { + "add": { + "headers": [ + "x-service:CHANGED" + ] + } + } + routes: + - name: r1 + id: 37fc74bd-bac6-4bce-bd54-6ec4d341c1c1 + paths: + - /r1 + filter_chains: + - name: route + id: 7b95fe94-df9b-421a-8a7b-c4d8fddbf363 + filters: + - name: response_transformer + enabled: false + config: | + { + "add": { + "headers": [ + "x-route:test" + ] + } + } diff --git a/tests/integration/testdata/sync/033-filter-chains/update.yaml b/tests/integration/testdata/sync/033-filter-chains/update.yaml new file mode 100644 index 000000000..bc53e4b9f --- /dev/null +++ b/tests/integration/testdata/sync/033-filter-chains/update.yaml @@ -0,0 +1,32 @@ +_format_version: "3.0" +services: +- name: test + id: 58076db2-28b6-423b-ba39-a797193017f7 + host: test + port: 8080 + protocol: http + filter_chains: + - name: service + id: d3ed9313-acf2-4982-af3c-83d9b52795c3 + filters: + - name: response_transformer + config: + add: + headers: + - x-service:CHANGED + routes: + - name: r1 + id: 37fc74bd-bac6-4bce-bd54-6ec4d341c1c1 + paths: + - /r1 + filter_chains: + - name: route + id: 7b95fe94-df9b-421a-8a7b-c4d8fddbf363 + filters: + - name: response_transformer + enabled: false + config: + add: + headers: + - x-route:test + diff --git a/validate/validate.go b/validate/validate.go index 85ba71705..fe15ad100 100644 --- a/validate/validate.go +++ b/validate/validate.go @@ -178,6 +178,9 @@ func (v *Validator) Validate(formatVersion semver.Version) []error { if err := v.entities(v.state.Upstreams, "upstreams"); err != nil { allErr = append(allErr, err...) } + if err := v.entities(v.state.FilterChains, "filter_chains"); err != nil { + allErr = append(allErr, err...) + } // validate routes format with Kong 3.x parsed30, err := semver.ParseTolerant(utils.FormatVersion30)