diff --git a/artifacts/flagger/crd.yaml b/artifacts/flagger/crd.yaml index b4f490ea..3713834f 100644 --- a/artifacts/flagger/crd.yaml +++ b/artifacts/flagger/crd.yaml @@ -172,15 +172,23 @@ spec: description: URI match conditions type: array items: - type: object properties: - uri: - type: object + authority: oneOf: - - required: ["exact"] - - required: ["prefix"] - - required: ["suffix"] - - required: ["regex"] + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex properties: exact: format: string @@ -188,12 +196,223 @@ spec: prefix: format: string type: string - suffix: + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + gateways: + description: + Names of gateways where the rule should be + applied. + items: + format: string + type: string + type: array + headers: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + type: object + ignoreUriCase: + description: + Flag to specify whether the URI matching should + be case-insensitive. + type: boolean + method: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: format: string type: string regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). format: string type: string + type: object + name: + description: The name assigned to a match. + format: string + type: string + port: + description: + Specifies the ports on the host that is being + addressed. + type: integer + queryParams: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + description: Query parameters for matching. + type: object + scheme: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + sourceLabels: + additionalProperties: + format: string + type: string + type: object + sourceNamespace: + description: + Source namespace constraining the applicability + of a rule to workloads in that namespace. + format: string + type: string + uri: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + withoutHeaders: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + description: + withoutHeader has the same syntax with the + header, but has opposite meaning. + type: object + type: object retries: description: Retry policy for HTTP requests type: object diff --git a/charts/flagger/crds/crd.yaml b/charts/flagger/crds/crd.yaml index b4f490ea..3713834f 100644 --- a/charts/flagger/crds/crd.yaml +++ b/charts/flagger/crds/crd.yaml @@ -172,15 +172,23 @@ spec: description: URI match conditions type: array items: - type: object properties: - uri: - type: object + authority: oneOf: - - required: ["exact"] - - required: ["prefix"] - - required: ["suffix"] - - required: ["regex"] + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex properties: exact: format: string @@ -188,12 +196,223 @@ spec: prefix: format: string type: string - suffix: + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + gateways: + description: + Names of gateways where the rule should be + applied. + items: + format: string + type: string + type: array + headers: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + type: object + ignoreUriCase: + description: + Flag to specify whether the URI matching should + be case-insensitive. + type: boolean + method: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: format: string type: string regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). format: string type: string + type: object + name: + description: The name assigned to a match. + format: string + type: string + port: + description: + Specifies the ports on the host that is being + addressed. + type: integer + queryParams: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + description: Query parameters for matching. + type: object + scheme: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + sourceLabels: + additionalProperties: + format: string + type: string + type: object + sourceNamespace: + description: + Source namespace constraining the applicability + of a rule to workloads in that namespace. + format: string + type: string + uri: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + withoutHeaders: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + description: + withoutHeader has the same syntax with the + header, but has opposite meaning. + type: object + type: object retries: description: Retry policy for HTTP requests type: object diff --git a/kustomize/base/flagger/crd.yaml b/kustomize/base/flagger/crd.yaml index b4f490ea..3713834f 100644 --- a/kustomize/base/flagger/crd.yaml +++ b/kustomize/base/flagger/crd.yaml @@ -172,15 +172,23 @@ spec: description: URI match conditions type: array items: - type: object properties: - uri: - type: object + authority: oneOf: - - required: ["exact"] - - required: ["prefix"] - - required: ["suffix"] - - required: ["regex"] + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex properties: exact: format: string @@ -188,12 +196,223 @@ spec: prefix: format: string type: string - suffix: + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + gateways: + description: + Names of gateways where the rule should be + applied. + items: + format: string + type: string + type: array + headers: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + type: object + ignoreUriCase: + description: + Flag to specify whether the URI matching should + be case-insensitive. + type: boolean + method: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: format: string type: string regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). format: string type: string + type: object + name: + description: The name assigned to a match. + format: string + type: string + port: + description: + Specifies the ports on the host that is being + addressed. + type: integer + queryParams: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + description: Query parameters for matching. + type: object + scheme: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + sourceLabels: + additionalProperties: + format: string + type: string + type: object + sourceNamespace: + description: + Source namespace constraining the applicability + of a rule to workloads in that namespace. + format: string + type: string + uri: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + withoutHeaders: + additionalProperties: + oneOf: + - not: + anyOf: + - required: + - exact + - required: + - prefix + - required: + - regex + - required: + - exact + - required: + - prefix + - required: + - regex + properties: + exact: + format: string + type: string + prefix: + format: string + type: string + regex: + description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). + format: string + type: string + type: object + description: + withoutHeader has the same syntax with the + header, but has opposite meaning. + type: object + type: object retries: description: Retry policy for HTTP requests type: object diff --git a/pkg/apis/istio/v1alpha3/virtual_service.go b/pkg/apis/istio/v1alpha3/virtual_service.go index b8c762e3..60f97628 100644 --- a/pkg/apis/istio/v1alpha3/virtual_service.go +++ b/pkg/apis/istio/v1alpha3/virtual_service.go @@ -394,6 +394,11 @@ type HeaderOperations struct { // // HTTPMatchRequest CANNOT be empty. type HTTPMatchRequest struct { + // The name assigned to a match. The match's name will be + // concatenated with the parent route's name and will be logged in + // the access logs for requests matching this route. + Name string `json:"name,omitempty"` + // URI to match // values are case-sensitive and formatted as follows: // @@ -467,6 +472,35 @@ type HTTPMatchRequest struct { // at the top of the VirtualService (if any) are overridden. The gateway match is // independent of sourceLabels. Gateways []string `json:"gateways,omitempty"` + + // Query parameters for matching. + // + // Ex: + // - For a query parameter like "?key=true", the map key would be "key" and + // the string match could be defined as `exact: "true"`. + // - For a query parameter like "?key", the map key would be "key" and the + // string match could be defined as `exact: ""`. + // - For a query parameter like "?key=123", the map key would be "key" and the + // string match could be defined as `regex: "\d+$"`. Note that this + // configuration will only match values like "123" but not "a123" or "123a". + // + // **Note:** `prefix` matching is currently not supported. + QueryParams map[string]v1alpha1.StringMatch `json:"queryParams,omitempty"` + + // Flag to specify whether the URI matching should be case-insensitive. + // + // **Note:** The case will be ignored only in the case of `exact` and `prefix` + // URI matches. + IgnoreUriCase bool `json:"ignoreUriCase,omitempty"` + + // withoutHeader has the same syntax with the header, but has opposite meaning. + // If a header is matched with a matching rule among withoutHeader, the traffic becomes not matched one. + WithoutHeaders map[string]v1alpha1.StringMatch `json:"withoutHeaders,omitempty"` + + // Source namespace constraining the applicability of a rule to workloads in that namespace. + // If the VirtualService has a list of gateways specified in the top-level `gateways` field, + // it must include the reserved gateway `mesh` for this field to be applicable. + SourceNamespace string `json:"sourceNamespace,omitempty"` } type DestinationWeight struct { diff --git a/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go b/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go index c0173ba2..61050fa0 100644 --- a/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go +++ b/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go @@ -329,6 +329,20 @@ func (in *HTTPMatchRequest) DeepCopyInto(out *HTTPMatchRequest) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.QueryParams != nil { + in, out := &in.QueryParams, &out.QueryParams + *out = make(map[string]v1alpha1.StringMatch, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.WithoutHeaders != nil { + in, out := &in.WithoutHeaders, &out.WithoutHeaders + *out = make(map[string]v1alpha1.StringMatch, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } return } diff --git a/pkg/router/istio.go b/pkg/router/istio.go index a42b6e6d..7cf6d6f1 100644 --- a/pkg/router/istio.go +++ b/pkg/router/istio.go @@ -440,15 +440,26 @@ func (ir *IstioRouter) Finalize(canary *flaggerv1.Canary) error { // mergeMatchConditions appends the URI match rules to canary conditions func mergeMatchConditions(canary, defaults []istiov1alpha3.HTTPMatchRequest) []istiov1alpha3.HTTPMatchRequest { - for i := range canary { + if len(defaults) == 0 { + return canary + } + + merged := make([]istiov1alpha3.HTTPMatchRequest, len(canary)*len(defaults)) + num := 0 + for _, c := range canary { for _, d := range defaults { - if d.Uri != nil { - canary[i].Uri = d.Uri + merged[num] = *d.DeepCopy() + if c.Headers != nil { + merged[num].Headers = c.Headers } + if c.SourceLabels != nil { + merged[num].SourceLabels = c.SourceLabels + } + num++ } } - return canary + return merged } // makeDestination returns a an destination weight for the specified host diff --git a/pkg/router/istio_test.go b/pkg/router/istio_test.go index 350acc57..5ec52012 100644 --- a/pkg/router/istio_test.go +++ b/pkg/router/istio_test.go @@ -27,6 +27,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1" + istiov1alpha1 "github.com/fluxcd/flagger/pkg/apis/istio/common/v1alpha1" istiov1alpha3 "github.com/fluxcd/flagger/pkg/apis/istio/v1alpha3" ) @@ -492,3 +493,74 @@ func TestIstioRouter_Finalize(t *testing.T) { } } } + +func TestIstioRouter_Match(t *testing.T) { + mocks := newFixture(nil) + router := &IstioRouter{ + logger: mocks.logger, + flaggerClient: mocks.flaggerClient, + istioClient: mocks.meshClient, + kubeClient: mocks.kubeClient, + } + + // service.match is not exists, analysis match is exists + err := router.Reconcile(mocks.abtest) + require.NoError(t, err) + + // test insert + vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get(context.TODO(), "abtest", metav1.GetOptions{}) + require.NoError(t, err) + assert.Len(t, vs.Spec.Http, 2) + assert.Len(t, vs.Spec.Http[0].Match, 1) // check for abtest-canary + require.Equal(t, vs.Spec.Http[0].Match[0].Headers["x-user-type"].Exact, "test") + assert.Len(t, vs.Spec.Http[1].Match, 0) // check for abtest-primary + + // Test Case that is service.match exists and multiple analysis.match + mocks.abtest.Spec.Service.Match = []istiov1alpha3.HTTPMatchRequest{ + { + Name: "podinfo", + Uri: &istiov1alpha1.StringMatch{ + Prefix: "/podinfo", + }, + Method: &istiov1alpha1.StringMatch{ + Exact: "GET", + }, + IgnoreUriCase: true, + }, + } + mocks.abtest.Spec.Analysis.Match = []istiov1alpha3.HTTPMatchRequest{ + { + Headers: map[string]istiov1alpha1.StringMatch{ + "x-user-type": { + Exact: "test", + }, + "x-auth-test": { + Exact: "test", + }, + }, + }, + { + Headers: map[string]istiov1alpha1.StringMatch{ + "x-session-id": { + Exact: "test", + }, + }, + }, + } + + // apply changes + err = router.Reconcile(mocks.abtest) + require.NoError(t, err) + + vs, err = mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get(context.TODO(), "abtest", metav1.GetOptions{}) + require.NoError(t, err) + assert.Len(t, vs.Spec.Http, 2) + assert.Len(t, vs.Spec.Http[0].Match, 2) // check for abtest-canary + require.Equal(t, vs.Spec.Http[0].Match[0].Uri.Prefix, "/podinfo") + require.Equal(t, vs.Spec.Http[0].Match[0].Headers["x-user-type"].Exact, "test") + require.Equal(t, vs.Spec.Http[0].Match[0].Headers["x-auth-test"].Exact, "test") + require.Equal(t, vs.Spec.Http[0].Match[1].Uri.Prefix, "/podinfo") + require.Equal(t, vs.Spec.Http[0].Match[1].Headers["x-session-id"].Exact, "test") + assert.Len(t, vs.Spec.Http[1].Match, 1) // check for abtest-primary + require.Equal(t, vs.Spec.Http[1].Match[0].Uri.Prefix, "/podinfo") +} diff --git a/pkg/router/router_test.go b/pkg/router/router_test.go index c6e2de9c..9ecbd93d 100644 --- a/pkg/router/router_test.go +++ b/pkg/router/router_test.go @@ -116,9 +116,16 @@ func newTestCanary() *flaggerv1.Canary { }, }, Match: []istiov1alpha3.HTTPMatchRequest{ - {Uri: &istiov1alpha1.StringMatch{ - Prefix: "/podinfo", - }}, + { + Name: "podinfo", + Uri: &istiov1alpha1.StringMatch{ + Prefix: "/podinfo", + }, + Method: &istiov1alpha1.StringMatch{ + Exact: "GET", + }, + IgnoreUriCase: true, + }, }, Retries: &istiov1alpha3.HTTPRetry{ Attempts: 10,