diff --git a/api/adc/types.go b/api/adc/types.go index db4d2d71b..018648719 100644 --- a/api/adc/types.go +++ b/api/adc/types.go @@ -194,15 +194,13 @@ type Timeout struct { // +k8s:deepcopy-gen=true type StreamRoute struct { - Description string `json:"description,omitempty"` - ID string `json:"id,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Name string `json:"name"` - Plugins Plugins `json:"plugins,omitempty"` - RemoteAddr string `json:"remote_addr,omitempty"` - ServerAddr string `json:"server_addr,omitempty"` - ServerPort *int64 `json:"server_port,omitempty"` - Sni string `json:"sni,omitempty"` + Metadata `json:",inline" yaml:",inline"` + + Plugins Plugins `json:"plugins,omitempty"` + RemoteAddr string `json:"remote_addr,omitempty"` + ServerAddr string `json:"server_addr,omitempty"` + ServerPort int32 `json:"server_port,omitempty"` + SNI string `json:"sni,omitempty"` } // +k8s:deepcopy-gen=true @@ -536,6 +534,24 @@ func ComposeRouteName(namespace, name string, rule string) string { return buf.String() } +// ComposeStreamRouteName uses namespace, name and rule name to compose +// the stream_route name. +func ComposeStreamRouteName(namespace, name string, rule string) string { + // FIXME Use sync.Pool to reuse this buffer if the upstream + // name composing code path is hot. + p := make([]byte, 0, len(namespace)+len(name)+len(rule)+6) + buf := bytes.NewBuffer(p) + + buf.WriteString(namespace) + buf.WriteByte('_') + buf.WriteString(name) + buf.WriteByte('_') + buf.WriteString(rule) + buf.WriteString("_tcp") + + return buf.String() +} + func ComposeServiceNameWithRule(namespace, name string, rule string) string { // FIXME Use sync.Pool to reuse this buffer if the upstream // name composing code path is hot. @@ -553,6 +569,24 @@ func ComposeServiceNameWithRule(namespace, name string, rule string) string { return buf.String() } +func ComposeServiceNameWithStream(namespace, name string, rule string) string { + // FIXME Use sync.Pool to reuse this buffer if the upstream + // name composing code path is hot. + var p []byte + plen := len(namespace) + len(name) + 6 + + p = make([]byte, 0, plen) + buf := bytes.NewBuffer(p) + buf.WriteString(namespace) + buf.WriteByte('_') + buf.WriteString(name) + buf.WriteByte('_') + buf.WriteString(rule) + buf.WriteString("_stream") + + return buf.String() +} + func ComposeConsumerName(namespace, name string) string { // FIXME Use sync.Pool to reuse this buffer if the upstream // name composing code path is hot. @@ -603,6 +637,18 @@ func NewDefaultRoute() *Route { } } +// NewDefaultStreamRoute returns an empty StreamRoute with default values. +func NewDefaultStreamRoute() *StreamRoute { + return &StreamRoute{ + Metadata: Metadata{ + Desc: "Created by apisix-ingress-controller, DO NOT modify it manually", + Labels: map[string]string{ + "managed-by": "apisix-ingress-controller", + }, + }, + } +} + const ( PluginProxyRewrite string = "proxy-rewrite" PluginRedirect string = "redirect" diff --git a/api/adc/zz_generated.deepcopy.go b/api/adc/zz_generated.deepcopy.go index b5ce7c00b..05ae110b4 100644 --- a/api/adc/zz_generated.deepcopy.go +++ b/api/adc/zz_generated.deepcopy.go @@ -554,19 +554,8 @@ func (in *Service) DeepCopy() *Service { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StreamRoute) DeepCopyInto(out *StreamRoute) { *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } + in.Metadata.DeepCopyInto(&out.Metadata) out.Plugins = in.Plugins.DeepCopy() - if in.ServerPort != nil { - in, out := &in.ServerPort, &out.ServerPort - *out = new(int64) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamRoute. diff --git a/api/v2/apisixroute_types.go b/api/v2/apisixroute_types.go index 6eef10acb..14776b6c2 100644 --- a/api/v2/apisixroute_types.go +++ b/api/v2/apisixroute_types.go @@ -39,7 +39,6 @@ type ApisixRouteSpec struct { HTTP []ApisixRouteHTTP `json:"http,omitempty" yaml:"http,omitempty"` // Stream defines a list of stream route rules. // Each rule specifies conditions to match TCP/UDP traffic and how to forward them. - // Stream is currently not supported. Stream []ApisixRouteStream `json:"stream,omitempty" yaml:"stream,omitempty"` } @@ -111,7 +110,9 @@ type ApisixRouteHTTP struct { type ApisixRouteStream struct { // Name is a unique identifier for the route. This field must not be empty. Name string `json:"name" yaml:"name"` - // Protocol specifies the L4 protocol to match. Can be `tcp` or `udp`. + // Protocol specifies the L4 protocol to match. Can be `TCP` or `UDP`. + // + // +kubebuilder:validation:Enum=TCP;UDP Protocol string `json:"protocol" yaml:"protocol"` // Match defines the criteria used to match incoming TCP or UDP connections. Match ApisixRouteStreamMatch `json:"match" yaml:"match"` @@ -226,6 +227,9 @@ type ApisixRouteAuthentication struct { type ApisixRouteStreamMatch struct { // IngressPort is the port on which the APISIX Ingress proxy server listens. // This must be a statically configured port, as APISIX does not support dynamic port binding. + // + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=65535 IngressPort int32 `json:"ingressPort" yaml:"ingressPort"` // Host is the destination host address used to match the incoming TCP/UDP traffic. Host string `json:"host,omitempty" yaml:"host,omitempty"` @@ -241,9 +245,12 @@ type ApisixRouteStreamBackend struct { // This can be either the port name or port number. ServicePort intstr.IntOrString `json:"servicePort" yaml:"servicePort"` // ResolveGranularity determines how the backend service is resolved. - // Valid values are `endpoints` and `service`. When set to `endpoints`, + // Valid values are `endpoint` and `service`. When set to `endpoint`, // individual pod IPs will be used; otherwise, the Service's ClusterIP or ExternalIP is used. - // The default is `endpoints`. + // The default is `endpoint`. + // + // +kubebuilder:default=endpoint + // +kubebuilder:validation:Enum=endpoint;service ResolveGranularity string `json:"resolveGranularity,omitempty" yaml:"resolveGranularity,omitempty"` // Subset specifies a named subset of the target Service. // The subset must be pre-defined in the corresponding ApisixUpstream resource. diff --git a/api/v2/shared_types.go b/api/v2/shared_types.go index 8ea57efae..06dae1d65 100644 --- a/api/v2/shared_types.go +++ b/api/v2/shared_types.go @@ -45,6 +45,11 @@ const ( DefaultWeight = 100 ) +const ( + ResolveGranularityService = "service" + ResolveGranularityEndpoint = "endpoint" +) + const ( // OpEqual means the equal ("==") operator in nginxVars. OpEqual = "Equal" diff --git a/config/crd/bases/apisix.apache.org_apisixroutes.yaml b/config/crd/bases/apisix.apache.org_apisixroutes.yaml index 419aa938e..963015668 100644 --- a/config/crd/bases/apisix.apache.org_apisixroutes.yaml +++ b/config/crd/bases/apisix.apache.org_apisixroutes.yaml @@ -356,7 +356,6 @@ spec: description: |- Stream defines a list of stream route rules. Each rule specifies conditions to match TCP/UDP traffic and how to forward them. - Stream is currently not supported. items: description: ApisixRouteStream defines the configuration for a Layer 4 (TCP/UDP) route. Currently not supported. @@ -366,11 +365,15 @@ spec: traffic should be forwarded. properties: resolveGranularity: + default: endpoint description: |- ResolveGranularity determines how the backend service is resolved. - Valid values are `endpoints` and `service`. When set to `endpoints`, + Valid values are `endpoint` and `service`. When set to `endpoint`, individual pod IPs will be used; otherwise, the Service's ClusterIP or ExternalIP is used. - The default is `endpoints`. + The default is `endpoint`. + enum: + - endpoint + - service type: string serviceName: description: |- @@ -408,6 +411,8 @@ spec: IngressPort is the port on which the APISIX Ingress proxy server listens. This must be a statically configured port, as APISIX does not support dynamic port binding. format: int32 + maximum: 65535 + minimum: 0 type: integer required: - ingressPort @@ -443,7 +448,10 @@ spec: type: array protocol: description: Protocol specifies the L4 protocol to match. Can - be `tcp` or `udp`. + be `TCP` or `UDP`. + enum: + - TCP + - UDP type: string required: - backend diff --git a/docs/en/latest/reference/api-reference.md b/docs/en/latest/reference/api-reference.md index 89c811dcb..9dbcdd19a 100644 --- a/docs/en/latest/reference/api-reference.md +++ b/docs/en/latest/reference/api-reference.md @@ -1178,7 +1178,7 @@ It defines routing rules for both HTTP and stream traffic. | --- | --- | | `ingressClassName` _string_ | IngressClassName is the name of the IngressClass this route belongs to. It allows multiple controllers to watch and reconcile different routes. | | `http` _[ApisixRouteHTTP](#apisixroutehttp) array_ | HTTP defines a list of HTTP route rules. Each rule specifies conditions to match HTTP requests and how to forward them. | -| `stream` _[ApisixRouteStream](#apisixroutestream) array_ | Stream defines a list of stream route rules. Each rule specifies conditions to match TCP/UDP traffic and how to forward them. Stream is currently not supported. | +| `stream` _[ApisixRouteStream](#apisixroutestream) array_ | Stream defines a list of stream route rules. Each rule specifies conditions to match TCP/UDP traffic and how to forward them. | _Appears in:_ @@ -1194,7 +1194,7 @@ ApisixRouteStream defines the configuration for a Layer 4 (TCP/UDP) route. Curre | Field | Description | | --- | --- | | `name` _string_ | Name is a unique identifier for the route. This field must not be empty. | -| `protocol` _string_ | Protocol specifies the L4 protocol to match. Can be `tcp` or `udp`. | +| `protocol` _string_ | Protocol specifies the L4 protocol to match. Can be `TCP` or `UDP`. | | `match` _[ApisixRouteStreamMatch](#apisixroutestreammatch)_ | Match defines the criteria used to match incoming TCP or UDP connections. | | `backend` _[ApisixRouteStreamBackend](#apisixroutestreambackend)_ | Backend specifies the destination service to which traffic should be forwarded. | | `plugins` _[ApisixRoutePlugin](#apisixrouteplugin) array_ | Plugins defines a list of plugins to apply to this route. | @@ -1214,7 +1214,7 @@ ApisixRouteStreamBackend represents the backend service for a TCP or UDP stream | --- | --- | | `serviceName` _string_ | ServiceName is the name of the Kubernetes Service. Cross-namespace references are not supported—ensure the ApisixRoute and the Service are in the same namespace. | | `servicePort` _[IntOrString](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#intorstring-intstr-util)_ | ServicePort is the port of the Kubernetes Service. This can be either the port name or port number. | -| `resolveGranularity` _string_ | ResolveGranularity determines how the backend service is resolved. Valid values are `endpoints` and `service`. When set to `endpoints`, individual pod IPs will be used; otherwise, the Service's ClusterIP or ExternalIP is used. The default is `endpoints`. | +| `resolveGranularity` _string_ | ResolveGranularity determines how the backend service is resolved. Valid values are `endpoint` and `service`. When set to `endpoint`, individual pod IPs will be used; otherwise, the Service's ClusterIP or ExternalIP is used. The default is `endpoint`. | | `subset` _string_ | Subset specifies a named subset of the target Service. The subset must be pre-defined in the corresponding ApisixUpstream resource. | diff --git a/go.mod b/go.mod index f8a6f76f8..aa09f673f 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,14 @@ toolchain go1.24.7 require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/api7/gopkg v0.2.1-0.20230601092738-0f3730f9b57a + github.com/eclipse/paho.mqtt.golang v1.5.0 github.com/gavv/httpexpect/v2 v2.16.0 github.com/go-logr/logr v1.4.2 github.com/go-logr/zapr v1.3.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.1 - github.com/gruntwork-io/terratest v0.47.0 + github.com/gorilla/websocket v1.5.3 + github.com/gruntwork-io/terratest v0.50.0 github.com/hashicorp/go-memdb v1.3.4 github.com/incubator4/go-resty-expr v0.1.1 github.com/onsi/ginkgo/v2 v2.22.0 @@ -40,9 +41,10 @@ require ( require ( cel.dev/expr v0.19.1 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect @@ -53,7 +55,42 @@ require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.44.245 // indirect + github.com/aws/aws-sdk-go-v2 v1.32.5 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect + github.com/aws/aws-sdk-go-v2/config v1.28.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.46 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect + github.com/aws/aws-sdk-go-v2/service/acm v1.30.6 // indirect + github.com/aws/aws-sdk-go-v2/service/autoscaling v1.51.0 // indirect + github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.44.0 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ecr v1.36.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ecs v1.52.0 // indirect + github.com/aws/aws-sdk-go-v2/service/iam v1.38.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.37.6 // indirect + github.com/aws/aws-sdk-go-v2/service/lambda v1.69.0 // indirect + github.com/aws/aws-sdk-go-v2/service/rds v1.91.0 // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.46.2 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0 // indirect + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sns v1.33.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sqs v1.37.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssm v1.56.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect + github.com/aws/smithy-go v1.22.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect @@ -66,7 +103,7 @@ require ( github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.5.0 // indirect - github.com/docker/cli v25.0.1+incompatible // indirect + github.com/docker/cli v27.1.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker v26.1.4+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect @@ -87,7 +124,7 @@ require ( github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -114,6 +151,10 @@ require ( github.com/imdario/mergo v0.3.16 // indirect github.com/imkira/go-interpol v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -146,7 +187,7 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/pquerna/otp v1.2.0 // indirect + github.com/pquerna/otp v1.4.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -160,7 +201,7 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/urfave/cli v1.22.14 // indirect + github.com/urfave/cli v1.22.16 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.34.0 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -172,7 +213,7 @@ require ( github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect diff --git a/go.sum b/go.sum index 1d46325bb..e7c1f08ca 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,15 @@ cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= @@ -44,8 +46,78 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.44.245 h1:KtY2s4q31/kn33AdV63R5t77mdxsI7rq3YT7Mgo805M= -github.com/aws/aws-sdk-go v1.44.245/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo= +github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= +github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0= +github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o= +github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41 h1:hqcxMc2g/MwwnRMod9n6Bd+t+9Nf7d5qRg7RaXKPd6o= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41/go.mod h1:d1eH0VrttvPmrCraU68LOyNdu26zFxQFjrVSb5vdhog= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg= +github.com/aws/aws-sdk-go-v2/service/acm v1.30.6 h1:fDg0RlN30Xf/yYzEUL/WXqhmgFsjVb/I3230oCfyI5w= +github.com/aws/aws-sdk-go-v2/service/acm v1.30.6/go.mod h1:zRR6jE3v/TcbfO8C2P+H0Z+kShiKKVaVyoIl8NQRjyg= +github.com/aws/aws-sdk-go-v2/service/autoscaling v1.51.0 h1:1KzQVZi7OTixxaVJ8fWaJAUBjme+iQ3zBOCZhE4RgxQ= +github.com/aws/aws-sdk-go-v2/service/autoscaling v1.51.0/go.mod h1:I1+/2m+IhnK5qEbhS3CrzjeiVloo9sItE/2K+so0fkU= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.44.0 h1:OREVd94+oXW5a+3SSUAo4K0L5ci8cucCLu+PSiek8OU= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.44.0/go.mod h1:Qbr4yfpNqVNl69l/GEDK+8wxLf/vHi0ChoiSDzD7thU= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1 h1:vucMirlM6D+RDU8ncKaSZ/5dGrXNajozVwpmWNPn2gQ= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1/go.mod h1:fceORfs010mNxZbQhfqUjUeHlTwANmIT4mvHamuUaUg= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0 h1:RhSoBFT5/8tTmIseJUXM6INTXTQDF8+0oyxWBnozIms= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0/go.mod h1:mzj8EEjIHSN2oZRXiw1Dd+uB4HZTl7hC8nBzX9IZMWw= +github.com/aws/aws-sdk-go-v2/service/ecr v1.36.6 h1:zg+3FGHA0PBs0KM25qE/rOf2o5zsjNa1g/Qq83+SDI0= +github.com/aws/aws-sdk-go-v2/service/ecr v1.36.6/go.mod h1:ZSq54Z9SIsOTf1Efwgw1msilSs4XVEfVQiP9nYVnKpM= +github.com/aws/aws-sdk-go-v2/service/ecs v1.52.0 h1:7/vgFWplkusJN/m+3QOa+W9FNRqa8ujMPNmdufRaJpg= +github.com/aws/aws-sdk-go-v2/service/ecs v1.52.0/go.mod h1:dPTOvmjJQ1T7Q+2+Xs2KSPrMvx+p0rpyV+HsQVnUK4o= +github.com/aws/aws-sdk-go-v2/service/iam v1.38.1 h1:hfkzDZHBp9jAT4zcd5mtqckpU4E3Ax0LQaEWWk1VgN8= +github.com/aws/aws-sdk-go-v2/service/iam v1.38.1/go.mod h1:u36ahDtZcQHGmVm/r+0L1sfKX4fzLEMdCqiKRKkUMVM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5 h1:3Y457U2eGukmjYjeHG6kanZpDzJADa2m0ADqnuePYVQ= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5/go.mod h1:CfwEHGkTjYZpkQ/5PvcbEtT7AJlG68KkEvmtwU8z3/U= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w= +github.com/aws/aws-sdk-go-v2/service/kms v1.37.6 h1:CZImQdb1QbU9sGgJ9IswhVkxAcjkkD1eQTMA1KHWk+E= +github.com/aws/aws-sdk-go-v2/service/kms v1.37.6/go.mod h1:YJDdlK0zsyxVBxGU48AR/Mi8DMrGdc1E3Yij4fNrONA= +github.com/aws/aws-sdk-go-v2/service/lambda v1.69.0 h1:BXt75frE/FYtAmEDBJRBa2HexOw+oAZWZl6QknZEFgg= +github.com/aws/aws-sdk-go-v2/service/lambda v1.69.0/go.mod h1:guz2K3x4FKSdDaoeB+TPVgJNU9oj2gftbp5cR8ela1A= +github.com/aws/aws-sdk-go-v2/service/rds v1.91.0 h1:eqHz3Uih+gb0vLE5Cc4Xf733vOxsxDp6GFUUVQU4d7w= +github.com/aws/aws-sdk-go-v2/service/rds v1.91.0/go.mod h1:h2jc7IleH3xHY7y+h8FH7WAZcz3IVLOB6/jXotIQ/qU= +github.com/aws/aws-sdk-go-v2/service/route53 v1.46.2 h1:wmt05tPp/CaRZpPV5B4SaJ5TwkHKom07/BzHoLdkY1o= +github.com/aws/aws-sdk-go-v2/service/route53 v1.46.2/go.mod h1:d+K9HESMpGb1EU9/UmmpInbGIUcAkwmcY6ZO/A3zZsw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0 h1:Q2ax8S21clKOnHhhr933xm3JxdJebql+R7aNo7p7GBQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6 h1:1KDMKvOKNrpD667ORbZ/+4OgvUoaok1gg/MLzrHF9fw= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6/go.mod h1:DmtyfCfONhOyVAJ6ZMTrDSFIeyCBlEO93Qkfhxwbxu0= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.6 h1:lEUtRHICiXsd7VRwRjXaY7MApT2X4Ue0Mrwe6XbyBro= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.6/go.mod h1:SODr0Lu3lFdT0SGsGX1TzFTapwveBrT5wztVoYtppm8= +github.com/aws/aws-sdk-go-v2/service/sqs v1.37.1 h1:39WvSrVq9DD6UHkD+fx5x19P5KpRQfNdtgReDVNbelc= +github.com/aws/aws-sdk-go-v2/service/sqs v1.37.1/go.mod h1:3gwPzC9LER/BTQdQZ3r6dUktb1rSjABF1D3Sr6nS7VU= +github.com/aws/aws-sdk-go-v2/service/ssm v1.56.0 h1:mADKqoZaodipGgiZfuAjtlcr4IVBtXPZKVjkzUZCCYM= +github.com/aws/aws-sdk-go-v2/service/ssm v1.56.0/go.mod h1:l9qF25TzH95FhcIak6e4vt79KE4I7M2Nf59eMUVjj6c= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg= +github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= +github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -85,7 +157,7 @@ github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -102,8 +174,8 @@ github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aB github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= -github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= +github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= @@ -118,6 +190,8 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o= +github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk= github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -173,8 +247,8 @@ github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -242,8 +316,8 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= @@ -252,8 +326,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/gruntwork-io/go-commons v0.8.0 h1:k/yypwrPqSeYHevLlEDmvmgQzcyTwrlZGRaxEM6G0ro= github.com/gruntwork-io/go-commons v0.8.0/go.mod h1:gtp0yTtIBExIZp7vyIV9I0XQkVwiQZze678hvDXof78= -github.com/gruntwork-io/terratest v0.47.0 h1:xIy1pT7NbGVlMLDZEHl3+3iSnvffh8tN2pL6idn448c= -github.com/gruntwork-io/terratest v0.47.0/go.mod h1:oywHw1cFKXSYvKPm27U7quZVzDUlA22H2xUrKCe26xM= +github.com/gruntwork-io/terratest v0.50.0 h1:AbBJ7IRCpLZ9H4HBrjeoWESITv8nLjN6/f1riMNcAsw= +github.com/gruntwork-io/terratest v0.50.0/go.mod h1:see0lbKvAqz6rvzvN2wyfuFQQG4PWcAb2yHulF6B2q4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -287,6 +361,14 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/incubator4/go-resty-expr v0.1.1 h1:9ur1M+p0wDzL1bprdGzHugGkfK0Yd3Ba/ijcgvL+a1k= github.com/incubator4/go-resty-expr v0.1.1/go.mod h1:w9YQkQLUs1cArOb4O7SGJwJL/L8kuAo6y5CVS2o9eag= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -416,8 +498,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= -github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= +github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= @@ -493,14 +575,15 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= +github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= @@ -538,8 +621,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= @@ -606,7 +689,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= @@ -644,7 +726,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= @@ -652,7 +733,6 @@ golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= @@ -735,8 +815,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= helm.sh/helm/v3 v3.15.4 h1:UFHd6oZ1IN3FsUZ7XNhOQDyQ2QYknBNWRHH57e9cbHY= helm.sh/helm/v3 v3.15.4/go.mod h1:phOwlxqGSgppCY/ysWBNRhG3MtnpsttOzxaTK+Mt40E= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/adc/translator/apisixroute.go b/internal/adc/translator/apisixroute.go index 4adcce6ab..e33d71e14 100644 --- a/internal/adc/translator/apisixroute.go +++ b/internal/adc/translator/apisixroute.go @@ -50,6 +50,14 @@ func (t *Translator) TranslateApisixRoute(tctx *provider.TranslateContext, ar *a } result.Services = append(result.Services, service) } + + for _, part := range ar.Spec.Stream { + service, err := t.translateStreamRule(tctx, ar, part) + if err != nil { + return nil, err + } + result.Services = append(result.Services, service) + } return result, nil } @@ -88,7 +96,7 @@ func (t *Translator) buildPlugins(tctx *provider.TranslateContext, ar *apiv2.Api t.loadPluginConfigPlugins(tctx, ar, rule, plugins) // Apply plugins from the route itself - t.loadRoutePlugins(tctx, ar, rule, plugins) + t.loadRoutePlugins(tctx, ar, rule.Plugins, plugins) // Add authentication plugins t.addAuthenticationPlugins(rule, plugins) @@ -121,8 +129,8 @@ func (t *Translator) loadPluginConfigPlugins(tctx *provider.TranslateContext, ar } } -func (t *Translator) loadRoutePlugins(tctx *provider.TranslateContext, ar *apiv2.ApisixRoute, rule apiv2.ApisixRouteHTTP, plugins adc.Plugins) { - for _, plugin := range rule.Plugins { +func (t *Translator) loadRoutePlugins(tctx *provider.TranslateContext, ar *apiv2.ApisixRoute, routePlugins []apiv2.ApisixRoutePlugin, plugins adc.Plugins) { + for _, plugin := range routePlugins { if !plugin.Enable { continue } @@ -210,7 +218,7 @@ func (t *Translator) buildUpstream(tctx *provider.TranslateContext, service *adc upstream, _ = t.translateApisixUpstream(tctx, au) } - if backend.ResolveGranularity == "service" { + if backend.ResolveGranularity == apiv2.ResolveGranularityService { upstream.Nodes, backendErr = t.translateApisixRouteBackendResolveGranularityService(tctx, utils.NamespacedName(ar), backend) if backendErr != nil { t.Log.Error(backendErr, "failed to translate ApisixRoute backend with ResolveGranularity Service") @@ -326,6 +334,20 @@ func (t *Translator) translateApisixRouteBackendResolveGranularityService(tctx * }, nil } +func (t *Translator) translateApisixRouteStreamBackendResolveGranularity(tctx *provider.TranslateContext, arNN types.NamespacedName, backend apiv2.ApisixRouteStreamBackend) (adc.UpstreamNodes, error) { + tsBackend := apiv2.ApisixRouteHTTPBackend{ + ServiceName: backend.ServiceName, + ServicePort: backend.ServicePort, + ResolveGranularity: backend.ResolveGranularity, + Subset: backend.Subset, + } + if backend.ResolveGranularity == apiv2.ResolveGranularityService { + return t.translateApisixRouteBackendResolveGranularityService(tctx, arNN, tsBackend) + } else { + return t.translateApisixRouteBackendResolveGranularityEndpoint(tctx, arNN, tsBackend) + } +} + func (t *Translator) translateApisixRouteBackendResolveGranularityEndpoint(tctx *provider.TranslateContext, arNN types.NamespacedName, backend apiv2.ApisixRouteHTTPBackend) (adc.UpstreamNodes, error) { weight := int32(*cmp.Or(backend.Weight, ptr.To(apiv2.DefaultWeight))) backendRef := gatewayv1.BackendRef{ @@ -340,3 +362,37 @@ func (t *Translator) translateApisixRouteBackendResolveGranularityEndpoint(tctx } return t.translateBackendRef(tctx, backendRef, DefaultEndpointFilter) } + +func (t *Translator) translateStreamRule(tctx *provider.TranslateContext, ar *apiv2.ApisixRoute, part apiv2.ApisixRouteStream) (*adc.Service, error) { + // add stream route plugins + plugins := make(adc.Plugins) + t.loadRoutePlugins(tctx, ar, part.Plugins, plugins) + + sr := adc.NewDefaultStreamRoute() + sr.Name = adc.ComposeStreamRouteName(ar.Namespace, ar.Name, part.Name) + sr.ID = id.GenID(sr.Name) + sr.ServerPort = part.Match.IngressPort + sr.SNI = part.Match.Host + sr.Plugins = plugins + + svc := adc.NewDefaultService() + svc.Name = adc.ComposeServiceNameWithStream(ar.Namespace, ar.Name, part.Name) + svc.ID = id.GenID(svc.Name) + svc.StreamRoutes = append(svc.StreamRoutes, sr) + + auNN := types.NamespacedName{Namespace: ar.GetNamespace(), Name: part.Backend.ServiceName} + upstream := adc.NewDefaultUpstream() + if au, ok := tctx.Upstreams[auNN]; ok { + upstream, _ = t.translateApisixUpstream(tctx, au) + } + nodes, err := t.translateApisixRouteStreamBackendResolveGranularity(tctx, utils.NamespacedName(ar), part.Backend) + if err != nil { + return nil, err + } + upstream.Nodes = nodes + upstream.ID = "" + upstream.Name = "" + + svc.Upstream = upstream + return svc, nil +} diff --git a/internal/controller/apisixroute_controller.go b/internal/controller/apisixroute_controller.go index d6f8c5150..73077b6d4 100644 --- a/internal/controller/apisixroute_controller.go +++ b/internal/controller/apisixroute_controller.go @@ -171,7 +171,7 @@ func (r *ApisixRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.Log.Error(err, "failed to process IngressClass parameters", "ingressClass", ic.Name) return ctrl.Result{}, client.IgnoreNotFound(err) } - if err = r.processApisixRoute(ctx, tctx, &ar); err != nil { + if err = r.processApisixRoute(tctx, &ar); err != nil { return ctrl.Result{}, err } if err = r.Provider.Update(ctx, tctx, &ar); err != nil { @@ -186,7 +186,7 @@ func (r *ApisixRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } -func (r *ApisixRouteReconciler) processApisixRoute(ctx context.Context, tc *provider.TranslateContext, in *apiv2.ApisixRoute) error { +func (r *ApisixRouteReconciler) processApisixRoute(tctx *provider.TranslateContext, in *apiv2.ApisixRoute) error { var ( rules = make(map[string]struct{}) ) @@ -201,18 +201,13 @@ func (r *ApisixRouteReconciler) processApisixRoute(ctx context.Context, tc *prov rules[http.Name] = struct{}{} // check secret - for _, plugin := range http.Plugins { - if !plugin.Enable { - continue - } - // check secret - if err := r.validateSecrets(ctx, tc, in, plugin.SecretRef); err != nil { - return err - } + if err := r.validatePlugins(tctx, in, http.Plugins); err != nil { + return err } + // check plugin config reference if http.PluginConfigName != "" { - if err := r.validatePluginConfig(ctx, tc, in, http); err != nil { + if err := r.validatePluginConfig(tctx, in, http); err != nil { return err } } @@ -234,11 +229,32 @@ func (r *ApisixRouteReconciler) processApisixRoute(ctx context.Context, tc *prov } // process backend - if err := r.validateBackends(ctx, tc, in, http); err != nil { + if err := r.validateHTTPBackends(tctx, in, http); err != nil { return err } // process upstreams - if err := r.validateUpstreams(ctx, tc, in, http); err != nil { + if err := r.validateUpstreams(tctx, in, http); err != nil { + return err + } + } + + for _, stream := range in.Spec.Stream { + // check rule names + if _, ok := rules[stream.Name]; ok { + return types.ReasonError{ + Reason: string(apiv2.ConditionReasonInvalidSpec), + Message: "duplicate route rule name", + } + } + rules[stream.Name] = struct{}{} + + // check secret + if err := r.validatePlugins(tctx, in, stream.Plugins); err != nil { + return err + } + + // process backend + if err := r.validateStreamBackend(tctx, in, stream); err != nil { return err } } @@ -246,7 +262,30 @@ func (r *ApisixRouteReconciler) processApisixRoute(ctx context.Context, tc *prov return nil } -func (r *ApisixRouteReconciler) validatePluginConfig(ctx context.Context, tc *provider.TranslateContext, in *apiv2.ApisixRoute, http apiv2.ApisixRouteHTTP) error { +func (r *ApisixRouteReconciler) validatePlugins(tctx *provider.TranslateContext, in *apiv2.ApisixRoute, plugins []apiv2.ApisixRoutePlugin) error { + // check secret + for _, plugin := range plugins { + if !plugin.Enable { + continue + } + // check secret + if err := r.validateSecrets(tctx, in, plugin.SecretRef); err != nil { + return err + } + } + return nil +} + +func (r *ApisixRouteReconciler) validateStreamBackend(tctx *provider.TranslateContext, in *apiv2.ApisixRoute, stream apiv2.ApisixRouteStream) error { + return r.validateHTTPBackend(tctx, apiv2.ApisixRouteHTTPBackend{ + ServiceName: stream.Backend.ServiceName, + ServicePort: stream.Backend.ServicePort, + Subset: stream.Backend.Subset, + ResolveGranularity: stream.Backend.ResolveGranularity, + }, in.GetNamespace()) +} + +func (r *ApisixRouteReconciler) validatePluginConfig(tctx *provider.TranslateContext, in *apiv2.ApisixRoute, http apiv2.ApisixRouteHTTP) error { pcNamespace := in.Namespace if http.PluginConfigNamespace != "" { pcNamespace = http.PluginConfigNamespace @@ -260,7 +299,7 @@ func (r *ApisixRouteReconciler) validatePluginConfig(ctx context.Context, tc *pr } pcNN = utils.NamespacedName(&pc) ) - if err := r.Get(ctx, pcNN, &pc); err != nil { + if err := r.Get(tctx, pcNN, &pc); err != nil { return types.ReasonError{ Reason: string(apiv2.ConditionReasonInvalidSpec), Message: fmt.Sprintf("failed to get ApisixPluginConfig: %s", pcNN), @@ -275,7 +314,7 @@ func (r *ApisixRouteReconciler) validatePluginConfig(ctx context.Context, tc *pr Version: r.ICGV.Version, Kind: types.KindIngressClass, }) - if err := r.Get(ctx, client.ObjectKey{Name: pc.Spec.IngressClassName}, ic); err != nil { + if err := r.Get(tctx, client.ObjectKey{Name: pc.Spec.IngressClassName}, ic); err != nil { return types.ReasonError{ Reason: string(apiv2.ConditionReasonInvalidSpec), Message: fmt.Sprintf("failed to get IngressClass %s for ApisixPluginConfig %s: %v", pc.Spec.IngressClassName, pcNN, err), @@ -290,21 +329,16 @@ func (r *ApisixRouteReconciler) validatePluginConfig(ctx context.Context, tc *pr } } - tc.ApisixPluginConfigs[pcNN] = &pc + tctx.ApisixPluginConfigs[pcNN] = &pc // Also check secrets referenced by plugin config - for _, plugin := range pc.Spec.Plugins { - if !plugin.Enable { - continue - } - if err := r.validateSecrets(ctx, tc, in, plugin.SecretRef); err != nil { - return err - } + if err := r.validatePlugins(tctx, in, pc.Spec.Plugins); err != nil { + return err } return nil } -func (r *ApisixRouteReconciler) validateSecrets(ctx context.Context, tc *provider.TranslateContext, in *apiv2.ApisixRoute, secretRef string) error { +func (r *ApisixRouteReconciler) validateSecrets(tctx *provider.TranslateContext, in *apiv2.ApisixRoute, secretRef string) error { if secretRef == "" { return nil } @@ -317,65 +351,61 @@ func (r *ApisixRouteReconciler) validateSecrets(ctx context.Context, tc *provide } secretNN = utils.NamespacedName(&secret) ) - if err := r.Get(ctx, secretNN, &secret); err != nil { + if err := r.Get(tctx, secretNN, &secret); err != nil { return types.ReasonError{ Reason: string(apiv2.ConditionReasonInvalidSpec), Message: fmt.Sprintf("failed to get Secret: %s", secretNN), } } - tc.Secrets[utils.NamespacedName(&secret)] = &secret + tctx.Secrets[utils.NamespacedName(&secret)] = &secret return nil } -func (r *ApisixRouteReconciler) processExternalNodes(ctx context.Context, tc *provider.TranslateContext, ups apiv2.ApisixUpstream) error { +func (r *ApisixRouteReconciler) processExternalNodes(tctx *provider.TranslateContext, ups apiv2.ApisixUpstream) error { for _, node := range ups.Spec.ExternalNodes { if node.Type == apiv2.ExternalTypeService { var ( service corev1.Service serviceNN = k8stypes.NamespacedName{Namespace: ups.GetNamespace(), Name: node.Name} ) - if err := r.Get(ctx, serviceNN, &service); err != nil { + if err := r.Get(tctx, serviceNN, &service); err != nil { r.Log.Error(err, "failed to get service in ApisixUpstream", "ApisixUpstream", ups.Name, "Service", serviceNN) if client.IgnoreNotFound(err) == nil { continue } return err } - tc.Services[utils.NamespacedName(&service)] = &service + tctx.Services[utils.NamespacedName(&service)] = &service } } return nil } -func (r *ApisixRouteReconciler) processTLSSecret(ctx context.Context, tc *provider.TranslateContext, ups apiv2.ApisixUpstream, secretNs string) error { +func (r *ApisixRouteReconciler) processTLSSecret(tctx *provider.TranslateContext, ups apiv2.ApisixUpstream) error { if ups.Spec.TLSSecret != nil && ups.Spec.TLSSecret.Name != "" { var ( secret corev1.Secret - secretNN = k8stypes.NamespacedName{Namespace: cmp.Or(ups.Spec.TLSSecret.Namespace, secretNs), Name: ups.Spec.TLSSecret.Name} + secretNN = k8stypes.NamespacedName{Namespace: cmp.Or(ups.Spec.TLSSecret.Namespace, ups.Namespace), Name: ups.Spec.TLSSecret.Name} ) - if err := r.Get(ctx, secretNN, &secret); err != nil { + if err := r.Get(tctx, secretNN, &secret); err != nil { r.Log.Error(err, "failed to get secret in ApisixUpstream", "ApisixUpstream", ups.Name, "Secret", secretNN) if client.IgnoreNotFound(err) != nil { return err } } - tc.Secrets[secretNN] = &secret + tctx.Secrets[secretNN] = &secret } return nil } -func (r *ApisixRouteReconciler) validateBackends(ctx context.Context, tc *provider.TranslateContext, in *apiv2.ApisixRoute, http apiv2.ApisixRouteHTTP) error { +func (r *ApisixRouteReconciler) validateHTTPBackends(tctx *provider.TranslateContext, in *apiv2.ApisixRoute, http apiv2.ApisixRouteHTTP) error { var backends = make(map[k8stypes.NamespacedName]struct{}) for _, backend := range http.Backends { - var ( - au apiv2.ApisixUpstream - service corev1.Service - serviceNN = k8stypes.NamespacedName{ - Namespace: in.GetNamespace(), - Name: backend.ServiceName, - } - ) + serviceNN := k8stypes.NamespacedName{ + Namespace: in.GetNamespace(), + Name: backend.ServiceName, + } if _, ok := backends[serviceNN]; ok { return types.ReasonError{ Reason: string(apiv2.ConditionReasonInvalidSpec), @@ -383,64 +413,79 @@ func (r *ApisixRouteReconciler) validateBackends(ctx context.Context, tc *provid } } backends[serviceNN] = struct{}{} - - if err := r.Get(ctx, serviceNN, &service); err != nil { - if err = client.IgnoreNotFound(err); err == nil { - r.Log.Error(errors.New("service not found"), "Service", serviceNN) - continue - } + if err := r.validateHTTPBackend(tctx, backend, in.GetNamespace()); err != nil { return err } + } - // try to get apisixupstream with the same name as the backend service - log.Debugw("try to get apisixupstream with the same name as the backend service", zap.Stringer("Service", serviceNN)) - if err := r.Get(ctx, serviceNN, &au); err != nil { - log.Debugw("no ApisixUpstream with the same name as the backend service found", zap.Stringer("Service", serviceNN), zap.Error(err)) - if err = client.IgnoreNotFound(err); err != nil { - return err - } - } else { - tc.Upstreams[serviceNN] = &au - if err := r.processTLSSecret(ctx, tc, au, in.GetNamespace()); err != nil { - return err - } - } + return nil +} - if service.Spec.Type == corev1.ServiceTypeExternalName { - tc.Services[serviceNN] = &service - continue +func (r *ApisixRouteReconciler) validateHTTPBackend(tctx *provider.TranslateContext, backend apiv2.ApisixRouteHTTPBackend, ns string) error { + var ( + au apiv2.ApisixUpstream + service corev1.Service + serviceNN = k8stypes.NamespacedName{ + Namespace: ns, + Name: backend.ServiceName, } + ) - if backend.ResolveGranularity == "service" && service.Spec.ClusterIP == "" { - r.Log.Error(errors.New("service has no ClusterIP"), "Service", serviceNN, "ResolveGranularity", backend.ResolveGranularity) - continue + if err := r.Get(tctx, serviceNN, &service); err != nil { + if err = client.IgnoreNotFound(err); err == nil { + r.Log.Error(errors.New("service not found"), "Service", serviceNN) + return nil } + return err + } - if !slices.ContainsFunc(service.Spec.Ports, func(port corev1.ServicePort) bool { - return port.Port == int32(backend.ServicePort.IntValue()) - }) { - r.Log.Error(errors.New("port not found in service"), "Service", serviceNN, "port", backend.ServicePort.String()) - continue + // try to get apisixupstream with the same name as the backend service + log.Debugw("try to get apisixupstream with the same name as the backend service", zap.Stringer("Service", serviceNN)) + if err := r.Get(tctx, serviceNN, &au); err != nil { + log.Debugw("no ApisixUpstream with the same name as the backend service found", zap.Stringer("Service", serviceNN), zap.Error(err)) + if err = client.IgnoreNotFound(err); err != nil { + return err } - tc.Services[serviceNN] = &service + } else { + tctx.Upstreams[serviceNN] = &au + if err := r.processTLSSecret(tctx, au); err != nil { + return err + } + } - // backend.subset specifies a subset of upstream nodes. - // It specifies that the target pod's label should be a superset of the subset labels of the ApisixUpstream of the serviceName - subsetLabels := r.getSubsetLabels(tc, serviceNN, backend) + if service.Spec.Type == corev1.ServiceTypeExternalName { + tctx.Services[serviceNN] = &service + return nil + } - // Collect endpoints with EndpointSlice support and subset filtering - if err := resolveServiceEndpoints(ctx, r.Client, tc, serviceNN, r.supportsEndpointSlice, subsetLabels); err != nil { - return types.ReasonError{ - Reason: string(apiv2.ConditionReasonInvalidSpec), - Message: err.Error(), - } + if backend.ResolveGranularity == "service" && service.Spec.ClusterIP == "" { + r.Log.Error(errors.New("service has no ClusterIP"), "Service", serviceNN, "ResolveGranularity", backend.ResolveGranularity) + return nil + } + + if !slices.ContainsFunc(service.Spec.Ports, func(port corev1.ServicePort) bool { + return port.Port == int32(backend.ServicePort.IntValue()) + }) { + r.Log.Error(errors.New("port not found in service"), "Service", serviceNN, "port", backend.ServicePort.String()) + return nil + } + tctx.Services[serviceNN] = &service + + // backend.subset specifies a subset of upstream nodes. + // It specifies that the target pod's label should be a superset of the subset labels of the ApisixUpstream of the serviceName + subsetLabels := r.getSubsetLabels(tctx, serviceNN, backend.Subset) + + if err := resolveServiceEndpoints(tctx, r.Client, serviceNN, r.supportsEndpointSlice, subsetLabels); err != nil { + return types.ReasonError{ + Reason: string(apiv2.ConditionReasonInvalidSpec), + Message: err.Error(), } } return nil } -func (r *ApisixRouteReconciler) validateUpstreams(ctx context.Context, tc *provider.TranslateContext, ar *apiv2.ApisixRoute, http apiv2.ApisixRouteHTTP) error { +func (r *ApisixRouteReconciler) validateUpstreams(tctx *provider.TranslateContext, ar *apiv2.ApisixRoute, http apiv2.ApisixRouteHTTP) error { for _, upstream := range http.Upstreams { if upstream.Name == "" { continue @@ -452,20 +497,20 @@ func (r *ApisixRouteReconciler) validateUpstreams(ctx context.Context, tc *provi Name: upstream.Name, } ) - if err := r.Get(ctx, upsNN, &ups); err != nil { + if err := r.Get(tctx, upsNN, &ups); err != nil { r.Log.Error(err, "failed to get ApisixUpstream", "ApisixUpstream", upsNN) if client.IgnoreNotFound(err) == nil { continue } return err } - tc.Upstreams[upsNN] = &ups + tctx.Upstreams[upsNN] = &ups - if err := r.processExternalNodes(ctx, tc, ups); err != nil { + if err := r.processExternalNodes(tctx, ups); err != nil { return err } - if err := r.processTLSSecret(ctx, tc, ups, ar.GetNamespace()); err != nil { + if err := r.processTLSSecret(tctx, ups); err != nil { return err } } @@ -671,8 +716,8 @@ func (r *ApisixRouteReconciler) listApisixRoutesForPluginConfig(ctx context.Cont return pkgutils.DedupComparable(requests) } -func (r *ApisixRouteReconciler) getSubsetLabels(tctx *provider.TranslateContext, auNN k8stypes.NamespacedName, backend apiv2.ApisixRouteHTTPBackend) map[string]string { - if backend.Subset == "" { +func (r *ApisixRouteReconciler) getSubsetLabels(tctx *provider.TranslateContext, auNN k8stypes.NamespacedName, subset string) map[string]string { + if subset == "" { return nil } @@ -682,9 +727,9 @@ func (r *ApisixRouteReconciler) getSubsetLabels(tctx *provider.TranslateContext, } // try to get the subset labels from the ApisixUpstream subsets - for _, subset := range au.Spec.Subsets { - if backend.Subset == subset.Name { - return subset.Labels + for _, s := range au.Spec.Subsets { + if subset == s.Name { + return s.Labels } } diff --git a/internal/controller/gatewayproxy_controller.go b/internal/controller/gatewayproxy_controller.go index af7136eb2..2687b5632 100644 --- a/internal/controller/gatewayproxy_controller.go +++ b/internal/controller/gatewayproxy_controller.go @@ -145,7 +145,7 @@ func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request return reconcile.Result{}, err } tctx.Services[serviceNN] = service - if err := resolveServiceEndpoints(tctx, r.Client, tctx, serviceNN, r.supportsEndpointSlice, nil); err != nil { + if err := resolveServiceEndpoints(tctx, r.Client, serviceNN, r.supportsEndpointSlice, nil); err != nil { return reconcile.Result{}, err } } diff --git a/internal/controller/httproute_controller.go b/internal/controller/httproute_controller.go index 0a4630f63..a329a6a0c 100644 --- a/internal/controller/httproute_controller.go +++ b/internal/controller/httproute_controller.go @@ -571,7 +571,7 @@ func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.Transla tctx.Services[targetNN] = &service // Collect endpoints with EndpointSlice support - if err := resolveServiceEndpoints(tctx, r.Client, tctx, targetNN, r.supportsEndpointSlice, nil); err != nil { + if err := resolveServiceEndpoints(tctx, r.Client, targetNN, r.supportsEndpointSlice, nil); err != nil { r.Log.Error(err, "failed to collect endpoints", "Service", targetNN) terr = err continue diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index 7ae96685b..b8c14af61 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -594,7 +594,7 @@ func (r *IngressReconciler) processBackendService(tctx *provider.TranslateContex } // Collect endpoints with EndpointSlice support - if err := resolveServiceEndpoints(tctx, r.Client, tctx, serviceNS, r.supportsEndpointSlice, nil); err != nil { + if err := resolveServiceEndpoints(tctx, r.Client, serviceNS, r.supportsEndpointSlice, nil); err != nil { r.Log.Error(err, "failed to collect endpoints", "namespace", namespace, "name", backendService.Name) return err } diff --git a/internal/controller/utils.go b/internal/controller/utils.go index ccdb987bc..cd24041ea 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -1499,7 +1499,7 @@ func addProviderEndpointsToTranslateContext(tctx *provider.TranslateContext, c c } tctx.Services[serviceNN] = &service - return resolveServiceEndpoints(tctx, c, tctx, serviceNN, true, nil) + return resolveServiceEndpoints(tctx, c, serviceNN, true, nil) } func TypePredicate[T client.Object]() func(obj client.Object) bool { @@ -1556,16 +1556,15 @@ func watchEndpointSliceOrEndpoints(bdr *ctrl.Builder, supportsEndpointSlice bool // resolveServiceEndpoints collects endpoints and adds them to the translate context // It handles both EndpointSlice (K8s 1.19+) and Endpoints (K8s 1.18) APIs with automatic fallback func resolveServiceEndpoints( - ctx context.Context, - c client.Client, tctx *provider.TranslateContext, + c client.Client, serviceNN k8stypes.NamespacedName, supportsEndpointSlice bool, subsetLabels map[string]string, ) error { if supportsEndpointSlice { var endpoints discoveryv1.EndpointSliceList - if err := c.List(ctx, &endpoints, + if err := c.List(tctx, &endpoints, client.InNamespace(serviceNN.Namespace), client.MatchingLabels{ discoveryv1.LabelServiceName: serviceNN.Name, @@ -1578,12 +1577,12 @@ func resolveServiceEndpoints( tctx.EndpointSlices[serviceNN] = endpoints.Items } else { // Apply subset filtering - tctx.EndpointSlices[serviceNN] = filterEndpointSlicesBySubsetLabels(ctx, c, endpoints.Items, subsetLabels) + tctx.EndpointSlices[serviceNN] = filterEndpointSlicesBySubsetLabels(tctx, c, endpoints.Items, subsetLabels) } } else { // Fallback to Endpoints API for Kubernetes 1.18 compatibility var ep corev1.Endpoints - if err := c.Get(ctx, serviceNN, &ep); err != nil { + if err := c.Get(tctx, serviceNN, &ep); err != nil { if client.IgnoreNotFound(err) != nil { return fmt.Errorf("failed to get endpoints: %v", err) } @@ -1597,7 +1596,7 @@ func resolveServiceEndpoints( tctx.EndpointSlices[serviceNN] = convertedEndpointSlices } else { // Apply subset filtering to converted EndpointSlices - tctx.EndpointSlices[serviceNN] = filterEndpointSlicesBySubsetLabels(ctx, c, convertedEndpointSlices, subsetLabels) + tctx.EndpointSlices[serviceNN] = filterEndpointSlicesBySubsetLabels(tctx, c, convertedEndpointSlices, subsetLabels) } } } diff --git a/internal/provider/apisix/provider.go b/internal/provider/apisix/provider.go index 4742d6ab1..395ba62c7 100644 --- a/internal/provider/apisix/provider.go +++ b/internal/provider/apisix/provider.go @@ -154,7 +154,7 @@ func (d *apisixProvider) Update(ctx context.Context, tctx *provider.TranslateCon defer d.syncNotify() - return d.client.UpdateConfig(ctx, adcclient.Task{ + task := adcclient.Task{ Key: rk, Name: rk.String(), Labels: label.GenLabel(obj), @@ -167,7 +167,10 @@ func (d *apisixProvider) Update(ctx context.Context, tctx *provider.TranslateCon SSLs: result.SSL, Consumers: result.Consumers, }, - }) + } + log.Debugw("updating config", zap.Any("task", task)) + + return d.client.UpdateConfig(ctx, task) } func (d *apisixProvider) Delete(ctx context.Context, obj client.Object) error { diff --git a/test/e2e/crds/v2/streamroute.go b/test/e2e/crds/v2/streamroute.go new file mode 100644 index 000000000..fb59124e3 --- /dev/null +++ b/test/e2e/crds/v2/streamroute.go @@ -0,0 +1,222 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package v2 + +import ( + "fmt" + "time" + + mqtt "github.com/eclipse/paho.mqtt.golang" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/apache/apisix-ingress-controller/test/e2e/framework" + "github.com/apache/apisix-ingress-controller/test/e2e/scaffold" +) + +var _ = Describe("Test ApisixRoute With StreamRoute", Label("apisix.apache.org", "v2", "apisixroute"), func() { + s := scaffold.NewDefaultScaffold() + + BeforeEach(func() { + if framework.ProviderType != framework.ProviderTypeAPISIX { + Skip("only support APISIX provider") + } + By("create GatewayProxy") + gatewayProxy := s.GetGatewayProxySpec() + err := s.CreateResourceFromString(gatewayProxy) + Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy") + time.Sleep(5 * time.Second) + + By("create IngressClass") + err = s.CreateResourceFromStringWithNamespace(s.GetIngressClassYaml(), "") + Expect(err).NotTo(HaveOccurred(), "creating IngressClass") + time.Sleep(5 * time.Second) + }) + + Context("TCP Proxy", func() { + apisixRoute := ` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + name: httpbin-tcp-route +spec: + ingressClassName: %s + stream: + - name: rule1 + protocol: TCP + match: + ingressPort: 9100 + backend: + serviceName: httpbin-service-e2e-test + servicePort: 80 +` + It("stream tcp proxy", func() { + err := s.CreateResourceFromString(fmt.Sprintf(apisixRoute, s.Namespace())) + Expect(err).NotTo(HaveOccurred(), "creating ApisixRoute") + + s.RequestAssert(&scaffold.RequestAssert{ + Client: s.NewAPISIXClientWithTCPProxy(), + Method: "GET", + Path: "/ip", + Checks: []scaffold.ResponseCheckFunc{ + scaffold.WithExpectedStatus(200), + scaffold.WithExpectedBodyContains("origin"), + }, + }) + + s.RequestAssert(&scaffold.RequestAssert{ + Client: s.NewAPISIXClientWithTCPProxy(), + Method: "GET", + Path: "/get", + Headers: map[string]string{ + "x-my-header": "x-my-value", + }, + Checks: []scaffold.ResponseCheckFunc{ + scaffold.WithExpectedStatus(200), + scaffold.WithExpectedBodyContains("x-my-value"), + }, + }) + }) + }) + + Context("UDP Proxy", func() { + apisixRoute := ` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + name: httpbin-udp-route +spec: + ingressClassName: %s + stream: + - name: rule1 + protocol: UDP + match: + ingressPort: 9200 + backend: + serviceName: %s + servicePort: %d +` + It("stream udp proxy", func() { + dnsSvc := s.NewCoreDNSService() + err := s.CreateResourceFromString(fmt.Sprintf(apisixRoute, s.Namespace(), dnsSvc.Name, dnsSvc.Spec.Ports[0].Port)) + Expect(err).NotTo(HaveOccurred(), "creating ApisixRoute") + time.Sleep(20 * time.Second) + + svc := s.GetDataplaneService() + + // test dns query + output, err := s.RunDigDNSClientFromK8s(fmt.Sprintf("@%s", svc.Name), "-p", "9200", "github.com") + Expect(err).NotTo(HaveOccurred(), "dig github.com via apisix udp proxy") + Expect(output).To(ContainSubstring("ADDITIONAL SECTION")) + + time.Sleep(3 * time.Second) + output = s.GetDeploymentLogs(scaffold.CoreDNSDeployment) + Expect(output).To(ContainSubstring("github.com. udp")) + }) + }) + + Context("Plugins", func() { + It("MQTT", func() { + //nolint:misspell // eclipse-mosquitto is the correct image name + mqttDeploy := ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mosquito +spec: + replicas: 1 + selector: + matchLabels: + app: mosquito + template: + metadata: + labels: + app: mosquito + spec: + containers: + - name: mosquito + image: eclipse-mosquitto:1.6 + livenessProbe: + tcpSocket: + port: 1883 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 1883 + initialDelaySeconds: 5 + periodSeconds: 10 + ports: + - name: mosquito + containerPort: 1883 + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + name: mosquito +spec: + selector: + app: mosquito + type: ClusterIP + ports: + - port: 1883 + targetPort: 1883 + protocol: TCP +` + apisixRoute := ` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + name: mqtt-route +spec: + ingressClassName: %s + stream: + - name: rule1 + protocol: TCP + match: + ingressPort: 9100 + backend: + serviceName: mosquito + servicePort: 1883 + plugins: + - name: mqtt-proxy + enable: true + config: + protocol_name: MQTT + protocol_level: 4 +` + err := s.CreateResourceFromString(mqttDeploy) + Expect(err).NotTo(HaveOccurred(), "creating mosquito deployment") + + s.WaitUntilDeploymentAvailable("mosquito") + + err = s.CreateResourceFromString(fmt.Sprintf(apisixRoute, s.Namespace())) + Expect(err).NotTo(HaveOccurred(), "creating ApisixRoute") + + s.RetryAssertion(func() error { + opts := mqtt.NewClientOptions() + opts.AddBroker(fmt.Sprintf("tcp://%s", s.GetAPISIXTCPEndpoint())) + mqttClient := mqtt.NewClient(opts) + token := mqttClient.Connect() + token.WaitTimeout(3 * time.Second) + return token.Error() + }).ShouldNot(HaveOccurred(), "connecting to mqtt proxy") + }) + }) +}) diff --git a/test/e2e/framework/manifests/apisix-standalone.yaml b/test/e2e/framework/manifests/apisix-standalone.yaml new file mode 100644 index 000000000..8c0a91fd7 --- /dev/null +++ b/test/e2e/framework/manifests/apisix-standalone.yaml @@ -0,0 +1,139 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: apisix-conf +data: + config.yaml: | + deployment: + role: traditional + role_traditional: + config_provider: yaml + admin: + allow_admin: + - 0.0.0.0/0 + admin_key: + - key: {{ .AdminKey }} + name: admin + role: admin + nginx_config: + worker_processes: 2 + error_log_level: info + apisix: + proxy_mode: http&stream + stream_proxy: # TCP/UDP proxy + tcp: # TCP proxy port list + - 9100 + udp: # UDP proxy port list + - 9200 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: apisix + labels: + app.kubernetes.io/name: apisix +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: apisix + template: + metadata: + labels: + app.kubernetes.io/name: apisix + spec: + initContainers: + - name: config-setup + image: apache/apisix:dev + command: + - sh + - -c + - | + echo "Copying default config directory to writable volume" + cp -r /usr/local/apisix/conf/* /tmp/apisix-conf/ + echo "Overwriting config.yaml with custom configuration" + cp /tmp/config-source/config.yaml /tmp/apisix-conf/config.yaml + echo "Config setup completed successfully" + ls -la /tmp/apisix-conf/ + volumeMounts: + - name: config-source + mountPath: /tmp/config-source + - name: config-writable + mountPath: /tmp/apisix-conf + containers: + - name: apisix + image: apache/apisix:dev + ports: + - name: http + containerPort: 9080 + protocol: TCP + - name: https + containerPort: 9443 + protocol: TCP + - name: admin + containerPort: 9180 + protocol: TCP + - name: tcp + containerPort: 9100 + protocol: TCP + - name: udp + containerPort: 9200 + protocol: UDP + volumeMounts: + - name: config-writable + mountPath: /usr/local/apisix/conf + volumes: + - name: config-source + configMap: + name: apisix-conf + - name: config-writable + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .ServiceName }} + labels: + app.kubernetes.io/name: apisix +spec: + ports: + - port: {{ .ServiceHTTPPort }} + name: http + protocol: TCP + targetPort: 9080 + - port: {{ .ServiceHTTPSPort }} + name: https + protocol: TCP + targetPort: 9443 + - port: 9180 + name: admin + protocol: TCP + targetPort: 9180 + - name: tcp + port: 9100 + protocol: TCP + targetPort: 9100 + - name: udp + port: 9200 + protocol: UDP + targetPort: 9200 + selector: + app.kubernetes.io/name: apisix + type: {{ .ServiceType | default "NodePort" }} diff --git a/test/e2e/framework/manifests/apisix.yaml b/test/e2e/framework/manifests/apisix.yaml index b01beca1d..88b836373 100644 --- a/test/e2e/framework/manifests/apisix.yaml +++ b/test/e2e/framework/manifests/apisix.yaml @@ -42,6 +42,13 @@ data: nginx_config: worker_processes: 2 error_log_level: info + apisix: + proxy_mode: http&stream + stream_proxy: # TCP/UDP proxy + tcp: # TCP proxy port list + - 9100 + udp: # UDP proxy port list + - 9200 --- apiVersion: apps/v1 kind: Deployment @@ -93,6 +100,12 @@ spec: - name: control containerPort: 9090 protocol: TCP + - name: tcp + containerPort: 9100 + protocol: TCP + - name: udp + containerPort: 9200 + protocol: UDP volumeMounts: - name: config-writable mountPath: /usr/local/apisix/conf @@ -130,6 +143,14 @@ spec: name: admin protocol: TCP targetPort: 9180 + - name: tcp + port: 9100 + protocol: TCP + targetPort: 9100 + - name: udp + port: 9200 + protocol: UDP + targetPort: 9200 selector: app.kubernetes.io/name: apisix type: {{ .ServiceType | default "NodePort" }} diff --git a/test/e2e/scaffold/api7_deployer.go b/test/e2e/scaffold/api7_deployer.go index b62ee04f9..0ac02fff2 100644 --- a/test/e2e/scaffold/api7_deployer.go +++ b/test/e2e/scaffold/api7_deployer.go @@ -26,7 +26,6 @@ import ( . "github.com/onsi/ginkgo/v2" //nolint:staticcheck . "github.com/onsi/gomega" //nolint:staticcheck corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" "github.com/apache/apisix-ingress-controller/pkg/utils" @@ -58,27 +57,11 @@ func (s *API7Deployer) BeforeEach() { s.runtimeOpts.ControllerName = fmt.Sprintf("%s/%s", DefaultControllerName, s.namespace) } s.finalizers = nil - if s.label == nil { - s.label = make(map[string]string) - } - if s.runtimeOpts.NamespaceSelectorLabel != nil { - for k, v := range s.runtimeOpts.NamespaceSelectorLabel { - if len(v) > 0 { - s.label[k] = v[0] - } - } - } else { - s.label["apisix.ingress.watch"] = s.namespace - } // Initialize additionalGatewayGroups map s.additionalGateways = make(map[string]*GatewayResources) - var nsLabel map[string]string - if !s.runtimeOpts.DisableNamespaceLabel { - nsLabel = s.label - } - k8s.CreateNamespaceWithMetadata(s.t, s.kubectlOptions, metav1.ObjectMeta{Name: s.namespace, Labels: nsLabel}) + k8s.CreateNamespace(s.t, s.kubectlOptions, s.namespace) s.nodes, err = k8s.GetReadyNodesE(s.t, s.kubectlOptions) Expect(err).NotTo(HaveOccurred(), "getting ready nodes") @@ -174,11 +157,8 @@ func (s *API7Deployer) DeployDataplane(deployOpts DeployDataplaneOptions) { deployOpts.SkipCreateTunnels = true } - for _, close := range []func(){ - s.closeApisixHttpTunnel, - s.closeApisixHttpsTunnel, - } { - close() + if s.apisixTunnels != nil { + s.apisixTunnels.Close() } svc := s.DeployGateway(&opts) @@ -197,13 +177,12 @@ func (s *API7Deployer) ScaleDataplane(replicas int) { } func (s *API7Deployer) newAPISIXTunnels(serviceName string) error { - httpTunnel, httpsTunnel, err := s.createDataplaneTunnels(s.dataplaneService, s.kubectlOptions, serviceName) + apisixTunnels, err := s.createDataplaneTunnels(s.dataplaneService, s.kubectlOptions, serviceName) if err != nil { return err } - s.apisixHttpTunnel = httpTunnel - s.apisixHttpsTunnel = httpsTunnel + s.apisixTunnels = apisixTunnels return nil } @@ -231,12 +210,7 @@ func (s *API7Deployer) CreateAdditionalGateway(namePrefix string) (string, *core // Create a new namespace for this gateway group additionalNS := fmt.Sprintf("%s-%d", namePrefix, time.Now().Unix()) - // Create namespace with the same labels - var nsLabel map[string]string - if !s.runtimeOpts.DisableNamespaceLabel { - nsLabel = s.label - } - k8s.CreateNamespaceWithMetadata(s.t, s.kubectlOptions, metav1.ObjectMeta{Name: additionalNS, Labels: nsLabel}) + k8s.CreateNamespace(s.t, s.kubectlOptions, additionalNS) // Create new kubectl options for the new namespace kubectlOpts := &k8s.KubectlOptions{ @@ -279,13 +253,12 @@ func (s *API7Deployer) CreateAdditionalGateway(namePrefix string) (string, *core resources.DataplaneService = svc // Create tunnels for the dataplane - httpTunnel, httpsTunnel, err := s.createDataplaneTunnels(svc, kubectlOpts, serviceName) + tunnels, err := s.createDataplaneTunnels(svc, kubectlOpts, serviceName) if err != nil { return "", nil, err } - resources.HttpTunnel = httpTunnel - resources.HttpsTunnel = httpsTunnel + resources.Tunnels = tunnels // Store in the map s.additionalGateways[gatewayGroupID] = resources diff --git a/test/e2e/scaffold/apisix_deployer.go b/test/e2e/scaffold/apisix_deployer.go index 704cf1fb6..91b6234e8 100644 --- a/test/e2e/scaffold/apisix_deployer.go +++ b/test/e2e/scaffold/apisix_deployer.go @@ -162,12 +162,14 @@ func (s *APISIXDeployer) DeployDataplane(deployOpts DeployDataplaneOptions) { for _, close := range []func(){ s.closeAdminTunnel, - s.closeApisixHttpTunnel, - s.closeApisixHttpsTunnel, } { close() } + if s.apisixTunnels != nil { + s.apisixTunnels.Close() + } + svc := s.deployDataplane(&opts) s.dataplaneService = svc @@ -181,13 +183,12 @@ func (s *APISIXDeployer) DeployDataplane(deployOpts DeployDataplaneOptions) { } func (s *APISIXDeployer) newAPISIXTunnels(serviceName string) error { - httpTunnel, httpsTunnel, err := s.createDataplaneTunnels(s.dataplaneService, s.kubectlOptions, serviceName) + apisixTunnels, err := s.createDataplaneTunnels(s.dataplaneService, s.kubectlOptions, serviceName) if err != nil { return err } - s.apisixHttpTunnel = httpTunnel - s.apisixHttpsTunnel = httpsTunnel + s.apisixTunnels = apisixTunnels return nil } @@ -356,13 +357,12 @@ func (s *APISIXDeployer) CreateAdditionalGateway(namePrefix string) (string, *co resources.DataplaneService = svc // Create tunnels for the dataplane - httpTunnel, httpsTunnel, err := s.createDataplaneTunnels(svc, kubectlOpts, svc.Name) + tunnels, err := s.createDataplaneTunnels(svc, kubectlOpts, svc.Name) if err != nil { return "", nil, err } - resources.HttpTunnel = httpTunnel - resources.HttpsTunnel = httpsTunnel + resources.Tunnels = tunnels // Use namespace as identifier for APISIX deployments identifier := additionalNS @@ -380,12 +380,7 @@ func (s *APISIXDeployer) CleanupAdditionalGateway(identifier string) error { } // Close tunnels if they exist - if resources.HttpTunnel != nil { - resources.HttpTunnel.Close() - } - if resources.HttpsTunnel != nil { - resources.HttpsTunnel.Close() - } + resources.Tunnels.Close() // Delete the namespace err := k8s.DeleteNamespaceE(s.t, &k8s.KubectlOptions{ diff --git a/test/e2e/scaffold/assertion.go b/test/e2e/scaffold/assertion.go index 30c8cd8ef..b17cab6bd 100644 --- a/test/e2e/scaffold/assertion.go +++ b/test/e2e/scaffold/assertion.go @@ -35,6 +35,24 @@ const ( type ResponseCheckFunc func(*HTTPResponse) error +// ErrorReporter implements httpexpect.Reporter +type ErrorReporter struct { + err error +} + +func (r *ErrorReporter) Errorf(message string, args ...interface{}) { + r.err = fmt.Errorf(message, args...) +} + +func (r *ErrorReporter) Fatalf(message string, args ...interface{}) { + r.err = fmt.Errorf(message, args...) +} + +// Err returns the stored error +func (r *ErrorReporter) Err() error { + return r.err +} + type HTTPResponse struct { *http.Response @@ -194,8 +212,10 @@ func (s *Scaffold) RequestAssert(r *RequestAssert) bool { r.Checks = append(r.Checks, r.Check) } - return EventuallyWithOffset(1, func() error { - req := r.request(r.Method, r.Path, r.Body) + return EventuallyWithOffset(1, func() (err error) { + reporter := &ErrorReporter{} + + req := r.request(r.Method, r.Path, r.Body).WithReporter(reporter) if len(r.Headers) > 0 { req = req.WithHeaders(r.Headers) } @@ -212,6 +232,10 @@ func (s *Scaffold) RequestAssert(r *RequestAssert) bool { } expResp := req.Expect() + if reporter.Err() != nil { + return reporter.Err() + } + resp := &HTTPResponse{ Response: expResp.Raw(), Body: expResp.Body().Raw(), diff --git a/test/e2e/scaffold/coredns.go b/test/e2e/scaffold/coredns.go new file mode 100644 index 000000000..0aadabec3 --- /dev/null +++ b/test/e2e/scaffold/coredns.go @@ -0,0 +1,93 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package scaffold + +import ( + "fmt" + + "github.com/gruntwork-io/terratest/modules/k8s" + ginkgo "github.com/onsi/ginkgo/v2" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" +) + +const ( + CoreDNSDeployment = "coredns" +) + +var ( + _udpDeployment = fmt.Sprintf(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: %s +spec: + replicas: 1 + selector: + matchLabels: + app: coredns + template: + metadata: + labels: + app: coredns + spec: + containers: + - name: coredns + image: coredns/coredns:1.8.4 + livenessProbe: + tcpSocket: + port: 53 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 53 + initialDelaySeconds: 2 + periodSeconds: 10 + ports: + - name: dns + containerPort: 53 + protocol: UDP +`, CoreDNSDeployment) + _udpService = ` +kind: Service +apiVersion: v1 +metadata: + name: coredns +spec: + selector: + app: coredns + type: ClusterIP + ports: + - port: 53 + targetPort: 53 +` +) + +// NewCoreDNSService creates a new UDP backend for testing. +func (s *Scaffold) NewCoreDNSService() *corev1.Service { + err := s.CreateResourceFromString(_udpDeployment) + assert.Nil(ginkgo.GinkgoT(), err, "failed to create CoreDNS deployment") + + err = s.CreateResourceFromString(_udpService) + assert.Nil(ginkgo.GinkgoT(), err, "failed to create CoreDNS service") + + s.EnsureNumEndpointsReady(ginkgo.GinkgoT(), "coredns", 1) + + svc, err := k8s.GetServiceE(s.t, s.kubectlOptions, "coredns") + assert.Nil(ginkgo.GinkgoT(), err, "failed to get CoreDNS service") + + return svc +} diff --git a/test/e2e/scaffold/k8s.go b/test/e2e/scaffold/k8s.go index 85ea6dc3c..cd13f3b4d 100644 --- a/test/e2e/scaffold/k8s.go +++ b/test/e2e/scaffold/k8s.go @@ -315,6 +315,10 @@ spec: return fmt.Sprintf(gatewayProxyYaml, framework.ProviderType, s.AdminKey()) } +func (s *Scaffold) WaitUntilDeploymentAvailable(name string) { + k8s.WaitUntilDeploymentAvailable(s.GinkgoT, s.kubectlOptions, name, 10, 10*time.Second) +} + const ingressClassYaml = ` apiVersion: networking.k8s.io/%s kind: IngressClass @@ -368,3 +372,18 @@ spec: func (s *Scaffold) GetGatewayYaml() string { return fmt.Sprintf(gatewayYaml, s.Namespace(), s.Namespace()) } + +func (s *Scaffold) RunDigDNSClientFromK8s(args ...string) (string, error) { + kubectlArgs := []string{ + "run", + "dig", + "-i", + "--rm", + "--restart=Never", + "--image-pull-policy=IfNotPresent", + "--image=toolbelt/dig", + "--", + } + kubectlArgs = append(kubectlArgs, args...) + return s.RunKubectlAndGetOutput(kubectlArgs...) +} diff --git a/test/e2e/scaffold/scaffold.go b/test/e2e/scaffold/scaffold.go index eababc306..6959decb6 100644 --- a/test/e2e/scaffold/scaffold.go +++ b/test/e2e/scaffold/scaffold.go @@ -27,6 +27,7 @@ import ( "strings" "time" + "github.com/api7/gopkg/pkg/log" "github.com/gavv/httpexpect/v2" "github.com/gruntwork-io/terratest/modules/k8s" "github.com/gruntwork-io/terratest/modules/testing" @@ -68,11 +69,8 @@ type Scaffold struct { dataplaneService *corev1.Service httpbinService *corev1.Service - finalizers []func() - label map[string]string - - apisixHttpTunnel *k8s.Tunnel - apisixHttpsTunnel *k8s.Tunnel + finalizers []func() + apisixTunnels *Tunnels additionalGateways map[string]*GatewayResources @@ -80,12 +78,42 @@ type Scaffold struct { Deployer Deployer } +type Tunnels struct { + HTTP *k8s.Tunnel + HTTPS *k8s.Tunnel + TCP *k8s.Tunnel +} + +func (t *Tunnels) Close() { + if t.HTTP != nil { + t.safeClose(t.HTTP.Close) + t.HTTP = nil + } + if t.HTTPS != nil { + t.safeClose(t.HTTPS.Close) + t.HTTPS = nil + } + if t.TCP != nil { + t.safeClose(t.TCP.Close) + t.TCP = nil + } +} + +func (t *Tunnels) safeClose(close func()) { + defer func() { + if r := recover(); r != nil { + log.Errorf("panic when closing tunnel: %v", r) + } + }() + + close() +} + // GatewayResources contains resources associated with a specific Gateway group type GatewayResources struct { Namespace string DataplaneService *corev1.Service - HttpTunnel *k8s.Tunnel - HttpsTunnel *k8s.Tunnel + Tunnels *Tunnels AdminAPIKey string } @@ -153,7 +181,7 @@ func (s *Scaffold) DefaultHTTPBackend() (string, []int32) { func (s *Scaffold) NewAPISIXClient() *httpexpect.Expect { u := url.URL{ Scheme: "http", - Host: s.apisixHttpTunnel.Endpoint(), + Host: s.apisixTunnels.HTTP.Endpoint(), } return httpexpect.WithConfig(httpexpect.Config{ BaseURL: u.String(), @@ -170,12 +198,16 @@ func (s *Scaffold) NewAPISIXClient() *httpexpect.Expect { } func (s *Scaffold) ApisixHTTPEndpoint() string { - return s.apisixHttpTunnel.Endpoint() + return s.apisixTunnels.HTTP.Endpoint() } // GetAPISIXHTTPSEndpoint get apisix https endpoint from tunnel map func (s *Scaffold) GetAPISIXHTTPSEndpoint() string { - return s.apisixHttpsTunnel.Endpoint() + return s.apisixTunnels.HTTPS.Endpoint() +} + +func (s *Scaffold) GetAPISIXTCPEndpoint() string { + return s.apisixTunnels.TCP.Endpoint() } func (s *Scaffold) UpdateNamespace(ns string) { @@ -186,7 +218,7 @@ func (s *Scaffold) UpdateNamespace(ns string) { func (s *Scaffold) NewAPISIXHttpsClient(host string) *httpexpect.Expect { u := url.URL{ Scheme: "https", - Host: s.apisixHttpsTunnel.Endpoint(), + Host: s.apisixTunnels.HTTPS.Endpoint(), } return httpexpect.WithConfig(httpexpect.Config{ BaseURL: u.String(), @@ -205,6 +237,26 @@ func (s *Scaffold) NewAPISIXHttpsClient(host string) *httpexpect.Expect { }) } +// NewAPISIXClientWithTCPProxy creates the HTTP client but with the TCP proxy of APISIX. +func (s *Scaffold) NewAPISIXClientWithTCPProxy() *httpexpect.Expect { + u := url.URL{ + Scheme: "http", + Host: s.apisixTunnels.TCP.Endpoint(), + } + return httpexpect.WithConfig(httpexpect.Config{ + BaseURL: u.String(), + Client: &http.Client{ + Transport: &http.Transport{}, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + }, + Reporter: httpexpect.NewAssertReporter( + httpexpect.NewAssertReporter(s.GinkgoT), + ), + }) +} + func (s *Scaffold) DefaultDataplaneResource() DataplaneResource { return s.Deployer.DefaultDataplaneResource() } @@ -269,22 +321,6 @@ func (s *Scaffold) DeleteResource(resourceType, name string) error { return k8s.RunKubectlE(s.t, s.kubectlOptions, "delete", resourceType, name) } -func (s *Scaffold) NamespaceSelectorLabelStrings() []string { - var labels []string - if s.opts.NamespaceSelectorLabel != nil { - for k, v := range s.opts.NamespaceSelectorLabel { - for _, v0 := range v { - labels = append(labels, fmt.Sprintf("%s=%s", k, v0)) - } - } - } else { - for k, v := range s.label { - labels = append(labels, fmt.Sprintf("%s=%s", k, v)) - } - } - return labels -} - func (s *Scaffold) NamespaceSelectorLabel() map[string][]string { return s.opts.NamespaceSelectorLabel } @@ -305,10 +341,11 @@ func (s *Scaffold) createDataplaneTunnels( svc *corev1.Service, kubectlOpts *k8s.KubectlOptions, serviceName string, -) (*k8s.Tunnel, *k8s.Tunnel, error) { +) (*Tunnels, error) { var ( httpPort int httpsPort int + tcpPort int ) for _, port := range svc.Spec.Ports { @@ -317,40 +354,37 @@ func (s *Scaffold) createDataplaneTunnels( httpPort = int(port.Port) case "https": httpsPort = int(port.Port) + case "tcp": + tcpPort = int(port.Port) } } + tunnels := &Tunnels{} + s.addFinalizers(tunnels.Close) + httpTunnel := k8s.NewTunnel(kubectlOpts, k8s.ResourceTypeService, serviceName, 0, httpPort) httpsTunnel := k8s.NewTunnel(kubectlOpts, k8s.ResourceTypeService, serviceName, 0, httpsPort) + tcpTunnel := k8s.NewTunnel(kubectlOpts, k8s.ResourceTypeService, serviceName, + 0, tcpPort) if err := httpTunnel.ForwardPortE(s.t); err != nil { - return nil, nil, err + return nil, err } - s.addFinalizers(s.closeApisixHttpTunnel) + tunnels.HTTP = httpTunnel if err := httpsTunnel.ForwardPortE(s.t); err != nil { - httpTunnel.Close() - return nil, nil, err + return nil, err } - s.addFinalizers(s.closeApisixHttpsTunnel) - - return httpTunnel, httpsTunnel, nil -} + tunnels.HTTPS = httpsTunnel -func (s *Scaffold) closeApisixHttpTunnel() { - if s.apisixHttpTunnel != nil { - s.apisixHttpTunnel.Close() - s.apisixHttpTunnel = nil + if err := tcpTunnel.ForwardPortE(s.t); err != nil { + return nil, err } -} + tunnels.TCP = tcpTunnel -func (s *Scaffold) closeApisixHttpsTunnel() { - if s.apisixHttpsTunnel != nil { - s.apisixHttpsTunnel.Close() - s.apisixHttpsTunnel = nil - } + return tunnels, nil } // GetAdditionalGateway returns resources associated with a specific gateway @@ -368,7 +402,7 @@ func (s *Scaffold) NewAPISIXClientForGateway(identifier string) (*httpexpect.Exp u := url.URL{ Scheme: "http", - Host: resources.HttpTunnel.Endpoint(), + Host: resources.Tunnels.HTTP.Endpoint(), } return httpexpect.WithConfig(httpexpect.Config{ BaseURL: u.String(), @@ -393,7 +427,7 @@ func (s *Scaffold) NewAPISIXHttpsClientForGateway(identifier string, host string u := url.URL{ Scheme: "https", - Host: resources.HttpsTunnel.Endpoint(), + Host: resources.Tunnels.HTTPS.Endpoint(), } return httpexpect.WithConfig(httpexpect.Config{ BaseURL: u.String(), @@ -419,7 +453,7 @@ func (s *Scaffold) GetGatewayHTTPEndpoint(identifier string) (string, error) { return "", fmt.Errorf("gateway %s not found", identifier) } - return resources.HttpTunnel.Endpoint(), nil + return resources.Tunnels.HTTP.Endpoint(), nil } // GetGatewayHTTPSEndpoint returns the HTTPS endpoint for a specific gateway @@ -429,7 +463,7 @@ func (s *Scaffold) GetGatewayHTTPSEndpoint(identifier string) (string, error) { return "", fmt.Errorf("gateway %s not found", identifier) } - return resources.HttpsTunnel.Endpoint(), nil + return resources.Tunnels.HTTPS.Endpoint(), nil } func (s *Scaffold) GetDataplaneService() *corev1.Service {