diff --git a/PROJECT b/PROJECT
index 11e8b0783..89be21ab1 100644
--- a/PROJECT
+++ b/PROJECT
@@ -26,4 +26,12 @@ resources:
kind: GatewayProxy
path: github.com/api7/api7-ingress-controller/api/v1alpha1
version: v1alpha1
+- api:
+ crdVersion: v1
+ namespaced: true
+ domain: github.com
+ group: gateway.apisix.io
+ kind: HTTPRoutePolicy
+ path: github.com/api7/api7-ingress-controller/api/v1alpha1
+ version: v1alpha1
version: "3"
diff --git a/api/adc/types.go b/api/adc/types.go
index 095bc5cf9..2e510b7f4 100644
--- a/api/adc/types.go
+++ b/api/adc/types.go
@@ -10,6 +10,8 @@ import (
"time"
"github.com/incubator4/go-resty-expr/expr"
+
+ "github.com/api7/api7-ingress-controller/api/common"
)
const (
@@ -113,16 +115,16 @@ type Service struct {
type Route struct {
Metadata `json:",inline" yaml:",inline"`
- EnableWebsocket *bool `json:"enable_websocket,omitempty" yaml:"enable_websocket,omitempty"`
- FilterFunc string `json:"filter_func,omitempty" yaml:"filter_func,omitempty"`
- Hosts []string `json:"hosts,omitempty" yaml:"hosts,omitempty"`
- Methods []string `json:"methods,omitempty" yaml:"methods,omitempty"`
- Plugins Plugins `json:"plugins,omitempty" yaml:"plugins,omitempty"`
- Priority *int64 `json:"priority,omitempty" yaml:"priority,omitempty"`
- RemoteAddrs []string `json:"remote_addrs,omitempty" yaml:"remote_addrs,omitempty"`
- Timeout *Timeout `json:"timeout,omitempty" yaml:"timeout,omitempty"`
- Uris []string `json:"uris" yaml:"uris"`
- Vars Vars `json:"vars,omitempty" yaml:"vars,omitempty"`
+ EnableWebsocket *bool `json:"enable_websocket,omitempty" yaml:"enable_websocket,omitempty"`
+ FilterFunc string `json:"filter_func,omitempty" yaml:"filter_func,omitempty"`
+ Hosts []string `json:"hosts,omitempty" yaml:"hosts,omitempty"`
+ Methods []string `json:"methods,omitempty" yaml:"methods,omitempty"`
+ Plugins Plugins `json:"plugins,omitempty" yaml:"plugins,omitempty"`
+ Priority *int64 `json:"priority,omitempty" yaml:"priority,omitempty"`
+ RemoteAddrs []string `json:"remote_addrs,omitempty" yaml:"remote_addrs,omitempty"`
+ Timeout *Timeout `json:"timeout,omitempty" yaml:"timeout,omitempty"`
+ Uris []string `json:"uris" yaml:"uris"`
+ Vars common.Vars `json:"vars,omitempty" yaml:"vars,omitempty"`
}
type Timeout struct {
@@ -325,62 +327,6 @@ func (n *UpstreamNodes) UnmarshalJSON(p []byte) error {
return nil
}
-// Vars represents the route match expressions of APISIX.
-type Vars [][]StringOrSlice
-
-// UnmarshalJSON implements json.Unmarshaler interface.
-// lua-cjson doesn't distinguish empty array and table,
-// and by default empty array will be encoded as '{}'.
-// We have to maintain the compatibility.
-func (vars *Vars) UnmarshalJSON(p []byte) error {
- if p[0] == '{' {
- if len(p) != 2 {
- return errors.New("unexpected non-empty object")
- }
- return nil
- }
- var data [][]StringOrSlice
- if err := json.Unmarshal(p, &data); err != nil {
- return err
- }
- *vars = data
- return nil
-}
-
-// StringOrSlice represents a string or a string slice.
-// TODO Do not use interface{} to avoid the reflection overheads.
-type StringOrSlice struct {
- StrVal string `json:"-"`
- SliceVal []string `json:"-"`
-}
-
-func (s *StringOrSlice) MarshalJSON() ([]byte, error) {
- var (
- p []byte
- err error
- )
- if s.SliceVal != nil {
- p, err = json.Marshal(s.SliceVal)
- } else {
- p, err = json.Marshal(s.StrVal)
- }
- return p, err
-}
-
-func (s *StringOrSlice) UnmarshalJSON(p []byte) error {
- var err error
-
- if len(p) == 0 {
- return errors.New("empty object")
- }
- if p[0] == '[' {
- err = json.Unmarshal(p, &s.SliceVal)
- } else {
- err = json.Unmarshal(p, &s.StrVal)
- }
- return err
-}
-
// ComposeRouteName uses namespace, name and rule name to compose
// the route name.
func ComposeRouteName(namespace, name string, rule string) string {
diff --git a/api/common/types.go b/api/common/types.go
new file mode 100644
index 000000000..358857ac3
--- /dev/null
+++ b/api/common/types.go
@@ -0,0 +1,64 @@
+// +kubebuilder:object:generate=true
+
+package common
+
+import (
+ "encoding/json"
+ "errors"
+)
+
+// Vars represents the route match expressions of APISIX.
+type Vars [][]StringOrSlice
+
+// UnmarshalJSON implements json.Unmarshaler interface.
+// lua-cjson doesn't distinguish empty array and table,
+// and by default empty array will be encoded as '{}'.
+// We have to maintain the compatibility.
+func (vars *Vars) UnmarshalJSON(p []byte) error {
+ if p[0] == '{' {
+ if len(p) != 2 {
+ return errors.New("unexpected non-empty object")
+ }
+ return nil
+ }
+ var data [][]StringOrSlice
+ if err := json.Unmarshal(p, &data); err != nil {
+ return err
+ }
+ *vars = data
+ return nil
+}
+
+// StringOrSlice represents a string or a string slice.
+// TODO Do not use interface{} to avoid the reflection overheads.
+type StringOrSlice struct {
+ StrVal string `json:"-"`
+ SliceVal []string `json:"-"`
+}
+
+func (s *StringOrSlice) MarshalJSON() ([]byte, error) {
+ var (
+ p []byte
+ err error
+ )
+ if s.SliceVal != nil {
+ p, err = json.Marshal(s.SliceVal)
+ } else {
+ p, err = json.Marshal(s.StrVal)
+ }
+ return p, err
+}
+
+func (s *StringOrSlice) UnmarshalJSON(p []byte) error {
+ var err error
+
+ if len(p) == 0 {
+ return errors.New("empty object")
+ }
+ if p[0] == '[' {
+ err = json.Unmarshal(p, &s.SliceVal)
+ } else {
+ err = json.Unmarshal(p, &s.StrVal)
+ }
+ return err
+}
diff --git a/api/common/zz_generated.deepcopy.go b/api/common/zz_generated.deepcopy.go
new file mode 100644
index 000000000..ee65e53d2
--- /dev/null
+++ b/api/common/zz_generated.deepcopy.go
@@ -0,0 +1,70 @@
+//go:build !ignore_autogenerated
+
+/*
+Copyright 2024.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package common
+
+import ()
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *StringOrSlice) DeepCopyInto(out *StringOrSlice) {
+ *out = *in
+ if in.SliceVal != nil {
+ in, out := &in.SliceVal, &out.SliceVal
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringOrSlice.
+func (in *StringOrSlice) DeepCopy() *StringOrSlice {
+ if in == nil {
+ return nil
+ }
+ out := new(StringOrSlice)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in Vars) DeepCopyInto(out *Vars) {
+ {
+ in := &in
+ *out = make(Vars, len(*in))
+ for i := range *in {
+ if (*in)[i] != nil {
+ in, out := &(*in)[i], &(*out)[i]
+ *out = make([]StringOrSlice, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Vars.
+func (in Vars) DeepCopy() Vars {
+ if in == nil {
+ return nil
+ }
+ out := new(Vars)
+ in.DeepCopyInto(out)
+ return *out
+}
diff --git a/api/v1alpha1/httproutepolicy_types.go b/api/v1alpha1/httproutepolicy_types.go
new file mode 100644
index 000000000..9da663d36
--- /dev/null
+++ b/api/v1alpha1/httproutepolicy_types.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2024.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+
+ "github.com/api7/api7-ingress-controller/api/common"
+)
+
+// HTTPRoutePolicySpec defines the desired state of HTTPRoutePolicy.
+type HTTPRoutePolicySpec struct {
+ // TargetRef identifies an API object (enum: HTTPRoute, Ingress) to apply HTTPRoutePolicy to.
+ //
+ // target references.
+ // +listType=map
+ // +listMapKey=group
+ // +listMapKey=kind
+ // +listMapKey=name
+ // +kubebuilder:validation:MinItems=1
+ // +kubebuilder:validation:MaxItems=16
+ TargetRefs []gatewayv1alpha2.LocalPolicyTargetReferenceWithSectionName `json:"targetRefs"`
+
+ Priority *int64 `json:"priority,omitempty" yaml:"priority,omitempty"`
+ Vars Vars `json:"vars,omitempty" yaml:"vars,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+
+// HTTPRoutePolicy is the Schema for the httproutepolicies API.
+type HTTPRoutePolicy struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec HTTPRoutePolicySpec `json:"spec,omitempty"`
+ Status gatewayv1alpha2.PolicyStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// HTTPRoutePolicyList contains a list of HTTPRoutePolicy.
+type HTTPRoutePolicyList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []HTTPRoutePolicy `json:"items"`
+}
+
+// Vars represents the route match expressions of APISIX.
+// +kubebuilder:object:generate=false
+type Vars = common.Vars
+
+func init() {
+ SchemeBuilder.Register(&HTTPRoutePolicy{}, &HTTPRoutePolicyList{})
+}
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 52915e5ab..948130c4a 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -21,6 +21,7 @@ limitations under the License.
package v1alpha1
import (
+ "github.com/api7/api7-ingress-controller/api/common"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
@@ -366,6 +367,105 @@ func (in *GatewayRef) DeepCopy() *GatewayRef {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HTTPRoutePolicy) DeepCopyInto(out *HTTPRoutePolicy) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoutePolicy.
+func (in *HTTPRoutePolicy) DeepCopy() *HTTPRoutePolicy {
+ if in == nil {
+ return nil
+ }
+ out := new(HTTPRoutePolicy)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *HTTPRoutePolicy) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HTTPRoutePolicyList) DeepCopyInto(out *HTTPRoutePolicyList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]HTTPRoutePolicy, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoutePolicyList.
+func (in *HTTPRoutePolicyList) DeepCopy() *HTTPRoutePolicyList {
+ if in == nil {
+ return nil
+ }
+ out := new(HTTPRoutePolicyList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *HTTPRoutePolicyList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HTTPRoutePolicySpec) DeepCopyInto(out *HTTPRoutePolicySpec) {
+ *out = *in
+ if in.TargetRefs != nil {
+ in, out := &in.TargetRefs, &out.TargetRefs
+ *out = make([]v1alpha2.LocalPolicyTargetReferenceWithSectionName, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ if in.Priority != nil {
+ in, out := &in.Priority, &out.Priority
+ *out = new(int64)
+ **out = **in
+ }
+ if in.Vars != nil {
+ in, out := &in.Vars, &out.Vars
+ *out = make(common.Vars, len(*in))
+ for i := range *in {
+ if (*in)[i] != nil {
+ in, out := &(*in)[i], &(*out)[i]
+ *out = make([]common.StringOrSlice, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoutePolicySpec.
+func (in *HTTPRoutePolicySpec) DeepCopy() *HTTPRoutePolicySpec {
+ if in == nil {
+ return nil
+ }
+ out := new(HTTPRoutePolicySpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoadBalancer) DeepCopyInto(out *LoadBalancer) {
*out = *in
diff --git a/config/crd/bases/gateway.apisix.io_httproutepolicies.yaml b/config/crd/bases/gateway.apisix.io_httproutepolicies.yaml
new file mode 100644
index 000000000..39b484fcd
--- /dev/null
+++ b/config/crd/bases/gateway.apisix.io_httproutepolicies.yaml
@@ -0,0 +1,474 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.15.0
+ name: httproutepolicies.gateway.apisix.io
+spec:
+ group: gateway.apisix.io
+ names:
+ kind: HTTPRoutePolicy
+ listKind: HTTPRoutePolicyList
+ plural: httproutepolicies
+ singular: httproutepolicy
+ scope: Namespaced
+ versions:
+ - name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: HTTPRoutePolicy is the Schema for the httproutepolicies API.
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: HTTPRoutePolicySpec defines the desired state of HTTPRoutePolicy.
+ properties:
+ priority:
+ format: int64
+ type: integer
+ targetRefs:
+ description: |-
+ TargetRef identifies an API object (enum: HTTPRoute, Ingress) to apply HTTPRoutePolicy to.
+
+
+ target references.
+ items:
+ description: |-
+ LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a
+ direct policy to. This should be used as part of Policy resources that can
+ target single resources. For more information on how this policy attachment
+ mode works, and a sample Policy resource, refer to the policy attachment
+ documentation for Gateway API.
+
+
+ Note: This should only be used for direct policy attachment when references
+ to SectionName are actually needed. In all other cases,
+ LocalPolicyTargetReference should be used.
+ properties:
+ group:
+ description: Group is the group of the target resource.
+ maxLength: 253
+ pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+ type: string
+ kind:
+ description: Kind is kind of the target resource.
+ maxLength: 63
+ minLength: 1
+ pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
+ type: string
+ name:
+ description: Name is the name of the target resource.
+ maxLength: 253
+ minLength: 1
+ type: string
+ sectionName:
+ description: |-
+ SectionName is the name of a section within the target resource. When
+ unspecified, this targetRef targets the entire resource. In the following
+ resources, SectionName is interpreted as the following:
+
+
+ * Gateway: Listener name
+ * HTTPRoute: HTTPRouteRule name
+ * Service: Port name
+
+
+ If a SectionName is specified, but does not exist on the targeted object,
+ the Policy must fail to attach, and the policy implementation should record
+ a `ResolvedRefs` or similar Condition in the Policy's status.
+ maxLength: 253
+ minLength: 1
+ pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+ type: string
+ required:
+ - group
+ - kind
+ - name
+ type: object
+ maxItems: 16
+ minItems: 1
+ type: array
+ x-kubernetes-list-map-keys:
+ - group
+ - kind
+ - name
+ x-kubernetes-list-type: map
+ vars:
+ description: Vars represents the route match expressions of APISIX.
+ items:
+ items:
+ description: |-
+ StringOrSlice represents a string or a string slice.
+ TODO Do not use interface{} to avoid the reflection overheads.
+ type: object
+ type: array
+ type: array
+ required:
+ - targetRefs
+ type: object
+ status:
+ description: |-
+ PolicyStatus defines the common attributes that all Policies should include within
+ their status.
+ properties:
+ ancestors:
+ description: |-
+ Ancestors is a list of ancestor resources (usually Gateways) that are
+ associated with the policy, and the status of the policy with respect to
+ each ancestor. When this policy attaches to a parent, the controller that
+ manages the parent and the ancestors MUST add an entry to this list when
+ the controller first sees the policy and SHOULD update the entry as
+ appropriate when the relevant ancestor is modified.
+
+
+ Note that choosing the relevant ancestor is left to the Policy designers;
+ an important part of Policy design is designing the right object level at
+ which to namespace this status.
+
+
+ Note also that implementations MUST ONLY populate ancestor status for
+ the Ancestor resources they are responsible for. Implementations MUST
+ use the ControllerName field to uniquely identify the entries in this list
+ that they are responsible for.
+
+
+ Note that to achieve this, the list of PolicyAncestorStatus structs
+ MUST be treated as a map with a composite key, made up of the AncestorRef
+ and ControllerName fields combined.
+
+
+ A maximum of 16 ancestors will be represented in this list. An empty list
+ means the Policy is not relevant for any ancestors.
+
+
+ If this slice is full, implementations MUST NOT add further entries.
+ Instead they MUST consider the policy unimplementable and signal that
+ on any related resources such as the ancestor that would be referenced
+ here. For example, if this list was full on BackendTLSPolicy, no
+ additional Gateways would be able to reference the Service targeted by
+ the BackendTLSPolicy.
+ items:
+ description: |-
+ PolicyAncestorStatus describes the status of a route with respect to an
+ associated Ancestor.
+
+
+ Ancestors refer to objects that are either the Target of a policy or above it
+ in terms of object hierarchy. For example, if a policy targets a Service, the
+ Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and
+ the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most
+ useful object to place Policy status on, so we recommend that implementations
+ SHOULD use Gateway as the PolicyAncestorStatus object unless the designers
+ have a _very_ good reason otherwise.
+
+
+ In the context of policy attachment, the Ancestor is used to distinguish which
+ resource results in a distinct application of this policy. For example, if a policy
+ targets a Service, it may have a distinct result per attached Gateway.
+
+
+ Policies targeting the same resource may have different effects depending on the
+ ancestors of those resources. For example, different Gateways targeting the same
+ Service may have different capabilities, especially if they have different underlying
+ implementations.
+
+
+ For example, in BackendTLSPolicy, the Policy attaches to a Service that is
+ used as a backend in a HTTPRoute that is itself attached to a Gateway.
+ In this case, the relevant object for status is the Gateway, and that is the
+ ancestor object referred to in this status.
+
+
+ Note that a parent is also an ancestor, so for objects where the parent is the
+ relevant object for status, this struct SHOULD still be used.
+
+
+ This struct is intended to be used in a slice that's effectively a map,
+ with a composite key made up of the AncestorRef and the ControllerName.
+ properties:
+ ancestorRef:
+ description: |-
+ AncestorRef corresponds with a ParentRef in the spec that this
+ PolicyAncestorStatus struct describes the status of.
+ properties:
+ group:
+ default: gateway.networking.k8s.io
+ description: |-
+ Group is the group of the referent.
+ When unspecified, "gateway.networking.k8s.io" is inferred.
+ To set the core API group (such as for a "Service" kind referent),
+ Group must be explicitly set to "" (empty string).
+
+
+ Support: Core
+ maxLength: 253
+ pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+ type: string
+ kind:
+ default: Gateway
+ description: |-
+ Kind is kind of the referent.
+
+
+ There are two kinds of parent resources with "Core" support:
+
+
+ * Gateway (Gateway conformance profile)
+ * Service (Mesh conformance profile, ClusterIP Services only)
+
+
+ Support for other resources is Implementation-Specific.
+ maxLength: 63
+ minLength: 1
+ pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
+ type: string
+ name:
+ description: |-
+ Name is the name of the referent.
+
+
+ Support: Core
+ maxLength: 253
+ minLength: 1
+ type: string
+ namespace:
+ description: |-
+ Namespace is the namespace of the referent. When unspecified, this refers
+ to the local namespace of the Route.
+
+
+ Note that there are specific rules for ParentRefs which cross namespace
+ boundaries. Cross-namespace references are only valid if they are explicitly
+ allowed by something in the namespace they are referring to. For example:
+ Gateway has the AllowedRoutes field, and ReferenceGrant provides a
+ generic way to enable any other kind of cross-namespace reference.
+
+
+
+ ParentRefs from a Route to a Service in the same namespace are "producer"
+ routes, which apply default routing rules to inbound connections from
+ any namespace to the Service.
+
+
+ ParentRefs from a Route to a Service in a different namespace are
+ "consumer" routes, and these routing rules are only applied to outbound
+ connections originating from the same namespace as the Route, for which
+ the intended destination of the connections are a Service targeted as a
+ ParentRef of the Route.
+
+
+
+ Support: Core
+ maxLength: 63
+ minLength: 1
+ pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+ type: string
+ port:
+ description: |-
+ Port is the network port this Route targets. It can be interpreted
+ differently based on the type of parent resource.
+
+
+ When the parent resource is a Gateway, this targets all listeners
+ listening on the specified port that also support this kind of Route(and
+ select this Route). It's not recommended to set `Port` unless the
+ networking behaviors specified in a Route must apply to a specific port
+ as opposed to a listener(s) whose port(s) may be changed. When both Port
+ and SectionName are specified, the name and port of the selected listener
+ must match both specified values.
+
+
+
+ When the parent resource is a Service, this targets a specific port in the
+ Service spec. When both Port (experimental) and SectionName are specified,
+ the name and port of the selected port must match both specified values.
+
+
+
+ Implementations MAY choose to support other parent resources.
+ Implementations supporting other types of parent resources MUST clearly
+ document how/if Port is interpreted.
+
+
+ For the purpose of status, an attachment is considered successful as
+ long as the parent resource accepts it partially. For example, Gateway
+ listeners can restrict which Routes can attach to them by Route kind,
+ namespace, or hostname. If 1 of 2 Gateway listeners accept attachment
+ from the referencing Route, the Route MUST be considered successfully
+ attached. If no Gateway listeners accept attachment from this Route,
+ the Route MUST be considered detached from the Gateway.
+
+
+ Support: Extended
+ format: int32
+ maximum: 65535
+ minimum: 1
+ type: integer
+ sectionName:
+ description: |-
+ SectionName is the name of a section within the target resource. In the
+ following resources, SectionName is interpreted as the following:
+
+
+ * Gateway: Listener name. When both Port (experimental) and SectionName
+ are specified, the name and port of the selected listener must match
+ both specified values.
+ * Service: Port name. When both Port (experimental) and SectionName
+ are specified, the name and port of the selected listener must match
+ both specified values.
+
+
+ Implementations MAY choose to support attaching Routes to other resources.
+ If that is the case, they MUST clearly document how SectionName is
+ interpreted.
+
+
+ When unspecified (empty string), this will reference the entire resource.
+ For the purpose of status, an attachment is considered successful if at
+ least one section in the parent resource accepts it. For example, Gateway
+ listeners can restrict which Routes can attach to them by Route kind,
+ namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from
+ the referencing Route, the Route MUST be considered successfully
+ attached. If no Gateway listeners accept attachment from this Route, the
+ Route MUST be considered detached from the Gateway.
+
+
+ Support: Core
+ maxLength: 253
+ minLength: 1
+ pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+ type: string
+ required:
+ - name
+ type: object
+ conditions:
+ description: Conditions describes the status of the Policy with
+ respect to the given Ancestor.
+ items:
+ description: "Condition contains details for one aspect of
+ the current state of this API Resource.\n---\nThis struct
+ is intended for direct use as an array at the field path
+ .status.conditions. For example,\n\n\n\ttype FooStatus
+ struct{\n\t // Represents the observations of a foo's
+ current state.\n\t // Known .status.conditions.type are:
+ \"Available\", \"Progressing\", and \"Degraded\"\n\t //
+ +patchMergeKey=type\n\t // +patchStrategy=merge\n\t //
+ +listType=map\n\t // +listMapKey=type\n\t Conditions
+ []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\"
+ patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t
+ \ // other fields\n\t}"
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False,
+ Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: |-
+ type of condition in CamelCase or in foo.example.com/CamelCase.
+ ---
+ Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
+ useful (see .node.status.conditions), the ability to deconflict is important.
+ The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ maxItems: 8
+ minItems: 1
+ type: array
+ x-kubernetes-list-map-keys:
+ - type
+ x-kubernetes-list-type: map
+ controllerName:
+ description: |-
+ ControllerName is a domain/path string that indicates the name of the
+ controller that wrote this status. This corresponds with the
+ controllerName field on GatewayClass.
+
+
+ Example: "example.net/gateway-controller".
+
+
+ The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are
+ valid Kubernetes names
+ (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).
+
+
+ Controllers MUST populate this field when writing status. Controllers should ensure that
+ entries to status populated with their ControllerName are cleaned up when they are no
+ longer necessary.
+ maxLength: 253
+ minLength: 1
+ pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$
+ type: string
+ required:
+ - ancestorRef
+ - controllerName
+ type: object
+ maxItems: 16
+ type: array
+ required:
+ - ancestors
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index d60b18110..4f099a894 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -6,6 +6,7 @@ resources:
- bases/gateway.apisix.io_gatewayproxies.yaml
- bases/gateway.apisix.io_consumers.yaml
- bases/gateway.apisix.io_backendtrafficpolicies.yaml
+- bases/gateway.apisix.io_httproutepolicies.yaml
# +kubebuilder:scaffold:crdkustomizeresource
patches:
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index e45ccade1..9addae723 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -93,6 +93,21 @@ rules:
- get
- list
- watch
+- apiGroups:
+ - gateway.apisix.io
+ resources:
+ - httproutepolicies
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - gateway.apisix.io
+ resources:
+ - httproutepolicies/status
+ verbs:
+ - get
+ - update
- apiGroups:
- gateway.apisix.io
resources:
diff --git a/config/samples/gateway.apisix.io_v1alpha1_httproutepolicy.yaml b/config/samples/gateway.apisix.io_v1alpha1_httproutepolicy.yaml
new file mode 100644
index 000000000..f471f01b5
--- /dev/null
+++ b/config/samples/gateway.apisix.io_v1alpha1_httproutepolicy.yaml
@@ -0,0 +1,9 @@
+apiVersion: gateway.apisix.io.github.com/v1alpha1
+kind: HTTPRoutePolicy
+metadata:
+ labels:
+ app.kubernetes.io/name: api7-ingress-controller
+ app.kubernetes.io/managed-by: kustomize
+ name: httproutepolicy-sample
+spec:
+ # TODO(user): Add fields here
diff --git a/go.mod b/go.mod
index f4da1ae13..2f07151f5 100644
--- a/go.mod
+++ b/go.mod
@@ -29,7 +29,6 @@ require (
go.uber.org/zap v1.27.0
golang.org/x/net v0.28.0
gopkg.in/yaml.v2 v2.4.0
- gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.25.11
helm.sh/helm/v3 v3.15.4
k8s.io/api v0.31.1
@@ -213,6 +212,7 @@ require (
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiserver v0.31.1 // indirect
k8s.io/cli-runtime v0.30.3 // indirect
k8s.io/component-base v0.31.1 // indirect
diff --git a/internal/manager/controllers.go b/internal/manager/controllers.go
index 852f2424d..603f6496e 100644
--- a/internal/manager/controllers.go
+++ b/internal/manager/controllers.go
@@ -26,6 +26,8 @@ import (
// +kubebuilder:rbac:groups=gateway.apisix.io,resources=consumers/status,verbs=get;update
// +kubebuilder:rbac:groups=gateway.apisix.io,resources=backendtrafficpolicies,verbs=get;list;watch
// +kubebuilder:rbac:groups=gateway.apisix.io,resources=backendtrafficpolicies/status,verbs=get;update
+// +kubebuilder:rbac:groups=gateway.apisix.io,resources=httproutepolicies,verbs=get;list;watch
+// +kubebuilder:rbac:groups=gateway.apisix.io,resources=httproutepolicies/status,verbs=get;update
// GatewayAPI
// +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gatewayclasses,verbs=get;list;watch;update
diff --git a/internal/provider/adc/translator/httproute.go b/internal/provider/adc/translator/httproute.go
index d285563dc..8dea67a97 100644
--- a/internal/provider/adc/translator/httproute.go
+++ b/internal/provider/adc/translator/httproute.go
@@ -5,13 +5,15 @@ import (
"fmt"
"strings"
- "github.com/api7/gopkg/pkg/log"
"github.com/pkg/errors"
"go.uber.org/zap"
discoveryv1 "k8s.io/api/discovery/v1"
"k8s.io/apimachinery/pkg/types"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+ "github.com/api7/api7-ingress-controller/api/common"
+ "github.com/api7/gopkg/pkg/log"
+
adctypes "github.com/api7/api7-ingress-controller/api/adc"
"github.com/api7/api7-ingress-controller/internal/controller/label"
"github.com/api7/api7-ingress-controller/internal/id"
@@ -354,14 +356,14 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match *gatewayv1.HTTPRouteMa
case gatewayv1.PathMatchPathPrefix:
route.Uris = []string{*match.Path.Value + "*"}
case gatewayv1.PathMatchRegularExpression:
- var this []adctypes.StringOrSlice
- this = append(this, adctypes.StringOrSlice{
+ var this []common.StringOrSlice
+ this = append(this, common.StringOrSlice{
StrVal: "uri",
})
- this = append(this, adctypes.StringOrSlice{
+ this = append(this, common.StringOrSlice{
StrVal: "~~",
})
- this = append(this, adctypes.StringOrSlice{
+ this = append(this, common.StringOrSlice{
StrVal: *match.Path.Value,
})
@@ -376,25 +378,25 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match *gatewayv1.HTTPRouteMa
name := strings.ToLower(string(header.Name))
name = strings.ReplaceAll(name, "-", "_")
- var this []adctypes.StringOrSlice
- this = append(this, adctypes.StringOrSlice{
+ var this []common.StringOrSlice
+ this = append(this, common.StringOrSlice{
StrVal: "http_" + name,
})
switch *header.Type {
case gatewayv1.HeaderMatchExact:
- this = append(this, adctypes.StringOrSlice{
+ this = append(this, common.StringOrSlice{
StrVal: "==",
})
case gatewayv1.HeaderMatchRegularExpression:
- this = append(this, adctypes.StringOrSlice{
+ this = append(this, common.StringOrSlice{
StrVal: "~~",
})
default:
return nil, errors.New("unknown header match type " + string(*header.Type))
}
- this = append(this, adctypes.StringOrSlice{
+ this = append(this, common.StringOrSlice{
StrVal: header.Value,
})
@@ -404,25 +406,25 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match *gatewayv1.HTTPRouteMa
if len(match.QueryParams) > 0 {
for _, query := range match.QueryParams {
- var this []adctypes.StringOrSlice
- this = append(this, adctypes.StringOrSlice{
+ var this []common.StringOrSlice
+ this = append(this, common.StringOrSlice{
StrVal: "arg_" + strings.ToLower(fmt.Sprintf("%v", query.Name)),
})
switch *query.Type {
case gatewayv1.QueryParamMatchExact:
- this = append(this, adctypes.StringOrSlice{
+ this = append(this, common.StringOrSlice{
StrVal: "==",
})
case gatewayv1.QueryParamMatchRegularExpression:
- this = append(this, adctypes.StringOrSlice{
+ this = append(this, common.StringOrSlice{
StrVal: "~~",
})
default:
return nil, errors.New("unknown query match type " + string(*query.Type))
}
- this = append(this, adctypes.StringOrSlice{
+ this = append(this, common.StringOrSlice{
StrVal: query.Value,
})