diff --git a/README.md b/README.md index 20bbbe7..2fa103a 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# k8s-apim-operator \ No newline at end of file +# k8s-apim-operator diff --git a/apim-operator/build/Dockerfile b/apim-operator/build/Dockerfile index 803cc02..65cecb7 100644 --- a/apim-operator/build/Dockerfile +++ b/apim-operator/build/Dockerfile @@ -6,6 +6,7 @@ ENV OPERATOR=/usr/local/bin/apim-operator \ # install operator binary COPY build/_output/bin/apim-operator ${OPERATOR} +COPY build/policy.mustache /usr/local/bin COPY build/bin /usr/local/bin RUN /usr/local/bin/user_setup diff --git a/apim-operator/build/policy.mustache b/apim-operator/build/policy.mustache new file mode 100644 index 0000000..cb8047e --- /dev/null +++ b/apim-operator/build/policy.mustache @@ -0,0 +1,58 @@ +import ballerina/io; +import ballerina/runtime; +import ballerina/http; +import ballerina/log; +import wso2/gateway; + +stream s{{name}}intermediateStream = new; +stream s{{name}}resultStream = new; +stream s{{name}}eligibilityStream = new; +stream s{{name}}reqCopy= gateway:requestStream; +stream s{{name}}globalThrotCopy = gateway:globalThrottleStream; + +function {{funcName}}() { + +forever { + from s{{name}}reqCopy + select s{{name}}reqCopy.messageID as messageID, (s{{name}}reqCopy.{{tierType}} == "{{name}}") as + isEligible, s{{name}}reqCopy.{{policyKey}} as throttleKey, 0 as expiryTimestamp + => (gateway:EligibilityStreamDTO[] counts) { + foreach var c in counts{ + s{{name}}eligibilityStream.publish(c); + } + } + + from s{{name}}eligibilityStream + throttler:timeBatch({{unitTime}}) + where s{{name}}eligibilityStream.isEligible == true + select s{{name}}eligibilityStream.throttleKey as throttleKey, count() as eventCount, {{stopOnQuotaReach}} as + stopOnQuota, expiryTimeStamp + group by s{{name}}eligibilityStream.throttleKey + => (gateway:IntermediateStream[] counts) { + foreach var c in counts{ + s{{name}}intermediateStream.publish(c); + } + } + + from s{{name}}intermediateStream + select s{{name}}intermediateStream.throttleKey, s{{name}}intermediateStream.eventCount>= {{count}} as isThrottled, + s{{name}}intermediateStream.stopOnQuota, s{{name}}intermediateStream.expiryTimeStamp + group by s{{name}}eligibilityStream.throttleKey + => (gateway:GlobalThrottleStreamDTO[] counts) { + foreach var c in counts{ + s{{name}}resultStream.publish(c); + } + } + + from s{{name}}resultStream + throttler:emitOnStateChange(s{{name}}resultStream.throttleKey, s{{name}}resultStream.isThrottled) + select s{{name}}resultStream.throttleKey as throttleKey, s{{name}}resultStream.isThrottled, + s{{name}}resultStream.stopOnQuota, s{{name}}resultStream.expiryTimeStamp + => (gateway:GlobalThrottleStreamDTO[] counts) { + foreach var c in counts{ + s{{name}}globalThrotCopy.publish(c); + } + } +} +} + diff --git a/apim-operator/cmd/manager/main.go b/apim-operator/cmd/manager/main.go index 4294e8c..cca85d4 100644 --- a/apim-operator/cmd/manager/main.go +++ b/apim-operator/cmd/manager/main.go @@ -13,7 +13,6 @@ import ( "github.com/apim-crd/apim-operator/pkg/apis" "github.com/apim-crd/apim-operator/pkg/controller" - "github.com/operator-framework/operator-sdk/pkg/k8sutil" "github.com/operator-framework/operator-sdk/pkg/leader" "github.com/operator-framework/operator-sdk/pkg/log/zap" "github.com/operator-framework/operator-sdk/pkg/metrics" @@ -62,12 +61,6 @@ func main() { printVersion() - namespace, err := k8sutil.GetWatchNamespace() - if err != nil { - log.Error(err, "Failed to get watch namespace") - os.Exit(1) - } - // Get a config to talk to the apiserver cfg, err := config.GetConfig() if err != nil { @@ -86,7 +79,7 @@ func main() { // Create a new Cmd to provide shared dependencies and start components mgr, err := manager.New(cfg, manager.Options{ - Namespace: namespace, + Namespace: "", MapperProvider: restmapper.NewDynamicRESTMapper, MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort), }) diff --git a/apim-operator/deploy/crds/wso2_v1alpha1_ratelimiting_cr.yaml b/apim-operator/deploy/crds/wso2_v1alpha1_ratelimiting_cr.yaml new file mode 100644 index 0000000..1ca1cca --- /dev/null +++ b/apim-operator/deploy/crds/wso2_v1alpha1_ratelimiting_cr.yaml @@ -0,0 +1,25 @@ +apiVersion: wso2.com/v1alpha1 +kind: RateLimiting +metadata: + name: testing1-ratelimiting +spec: + type: subscription # application ,subscription + description: Allow 1000 requests per minute # optional + timeUnit: min # min or sec + unitTime: 1 + requestCount: + limit: 1000 + bandwidth: # optional + dataAmount: "" + dataUnit: "" + stopOnQuotaReach: false # not required for application policies + conditions: # optional + headerCondition: + headerName: “host” + headerValue: “abc.com” + ipCondition: + type: ipRange + specificIp: "" + negation : no + startIp: 10.100.7.2 + endIp: 10.100.7.255 \ No newline at end of file diff --git a/apim-operator/deploy/crds/wso2_v1alpha1_ratelimiting_crd.yaml b/apim-operator/deploy/crds/wso2_v1alpha1_ratelimiting_crd.yaml new file mode 100644 index 0000000..28bc531 --- /dev/null +++ b/apim-operator/deploy/crds/wso2_v1alpha1_ratelimiting_crd.yaml @@ -0,0 +1,106 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: ratelimitings.wso2.com +spec: + group: wso2.com + names: + kind: RateLimiting + listKind: RateLimitingList + plural: ratelimitings + singular: ratelimiting + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + bandwidth: + properties: + dataAmount: + type: string + dataUnit: + type: string + required: + - dataAmount + - dataUnit + type: object + conditions: + properties: + headerCondition: + properties: + headerName: + type: string + headerValue: + type: string + required: + - headerName + - headerValue + type: object + ipCondition: + properties: + endIp: + type: string + negation: + type: boolean + specificIp: + type: string + startIp: + type: string + type: + type: string + required: + - type + - specificIp + - negation + - startIp + - endIp + type: object + required: + - headerCondition + - ipCondition + type: object + description: + type: string + requestCount: + properties: + limit: + format: int64 + type: integer + required: + - limit + type: object + stopOnQuotaReach: + type: boolean + timeUnit: + type: string + type: + type: string + unitTime: + format: int64 + type: integer + required: + - type + - timeUnit + - unitTime + - requestCount + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/apim-operator/deploy/namespace.yaml b/apim-operator/deploy/namespace.yaml new file mode 100644 index 0000000..a2b10b4 --- /dev/null +++ b/apim-operator/deploy/namespace.yaml @@ -0,0 +1,6 @@ +kind: Namespace +apiVersion: v1 +metadata: + name: wso2-system + labels: + name: wso2-system \ No newline at end of file diff --git a/apim-operator/deploy/operator.yaml b/apim-operator/deploy/operator.yaml index 03e8f68..552a831 100644 --- a/apim-operator/deploy/operator.yaml +++ b/apim-operator/deploy/operator.yaml @@ -2,6 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: apim-operator + namespace: wso2-system spec: replicas: 1 selector: @@ -16,15 +17,13 @@ spec: containers: - name: apim-operator # Replace this with the built image name - image: pubudu/apim-operator:1.0.0 + image: rameshakaru/api-operator:v0.0.1 command: - apim-operator imagePullPolicy: Always env: - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace + value: "" - name: POD_NAME valueFrom: fieldRef: diff --git a/apim-operator/deploy/role.yaml b/apim-operator/deploy/role.yaml index 5498f06..ddfb806 100644 --- a/apim-operator/deploy/role.yaml +++ b/apim-operator/deploy/role.yaml @@ -1,8 +1,9 @@ apiVersion: rbac.authorization.k8s.io/v1 -kind: Role +kind: ClusterRole metadata: creationTimestamp: null name: apim-operator + namespace: wso2-system rules: - apiGroups: - "" @@ -44,5 +45,6 @@ rules: - wso2.com resources: - '*' + - ratelimitings verbs: - '*' diff --git a/apim-operator/deploy/role_binding.yaml b/apim-operator/deploy/role_binding.yaml index b878550..bfd8c45 100644 --- a/apim-operator/deploy/role_binding.yaml +++ b/apim-operator/deploy/role_binding.yaml @@ -1,11 +1,14 @@ -kind: RoleBinding +kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: apim-operator + namespace: wso2-system subjects: - kind: ServiceAccount name: apim-operator + # Replace this with the namespace the operator is deployed in. + namespace: wso2-system roleRef: - kind: Role + kind: ClusterRole name: apim-operator apiGroup: rbac.authorization.k8s.io diff --git a/apim-operator/deploy/service_account.yaml b/apim-operator/deploy/service_account.yaml index a6dc42c..aee8d35 100644 --- a/apim-operator/deploy/service_account.yaml +++ b/apim-operator/deploy/service_account.yaml @@ -2,3 +2,4 @@ apiVersion: v1 kind: ServiceAccount metadata: name: apim-operator + namespace: wso2-system diff --git a/apim-operator/pkg/apis/wso2/v1alpha1/ratelimiting_types.go b/apim-operator/pkg/apis/wso2/v1alpha1/ratelimiting_types.go new file mode 100644 index 0000000..a38a42f --- /dev/null +++ b/apim-operator/pkg/apis/wso2/v1alpha1/ratelimiting_types.go @@ -0,0 +1,90 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// RateLimitingSpec defines the desired state of RateLimiting +// +k8s:openapi-gen=true +type RateLimitingSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html + + Type string `json:"type"` + TimeUnit string `json:"timeUnit"` + UnitTime int `json:"unitTime"` + RequestCount RequestCount `json:"requestCount"` + StopOnQuotaReach bool `json:"stopOnQuotaReach"` + Description string `json:"description"` + Bandwidth Bandwidth `json:"bandwidth"` + Conditions Conditions `json:"conditions"` +} + +//RequestCount is exported type in Ratelimiting Spec +type RequestCount struct { + Limit int `json:"limit"` +} + +//Bandwidth is exported type in Ratelimiting Spec +type Bandwidth struct { + DataAmount string `json:"dataAmount"` + DataUnit string `json:"dataUnit"` +} + +//Conditions is exported type in Ratelimiting Spec +type Conditions struct { + HeaderCondition HeaderCondition `json:"headerCondition"` + IPCondition IPCondition `json:"ipCondition"` +} + +//HeaderCondition is exported type in Ratelimiting Spec +type HeaderCondition struct { + HeaderName string `json:"headerName"` + HeaderValue string `json:"headerValue"` +} + +//IPCondition is exported type in Ratelimiting Spec +type IPCondition struct { + Type string `json:"type"` + SpecificIP string `json:"specificIp"` + Negation bool `json:"negation"` + StartIP string `json:"startIp"` + EndIP string `json:"endIp"` +} + +// RateLimitingStatus defines the observed state of RateLimiting +// +k8s:openapi-gen=true +type RateLimitingStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RateLimiting is the Schema for the ratelimitings API +// +k8s:openapi-gen=true +type RateLimiting struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RateLimitingSpec `json:"spec,omitempty"` + //Status RateLimitingStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RateLimitingList contains a list of RateLimiting +type RateLimitingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RateLimiting `json:"items"` +} + +func init() { + SchemeBuilder.Register(&RateLimiting{}, &RateLimitingList{}) +} diff --git a/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.deepcopy.go b/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.deepcopy.go index 4733362..495c4ff 100644 --- a/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.deepcopy.go +++ b/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.deepcopy.go @@ -155,6 +155,22 @@ func (in *APIStatus) DeepCopy() *APIStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Bandwidth) DeepCopyInto(out *Bandwidth) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Bandwidth. +func (in *Bandwidth) DeepCopy() *Bandwidth { + if in == nil { + return nil + } + out := new(Bandwidth) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BusinessInformation) DeepCopyInto(out *BusinessInformation) { *out = *in @@ -171,6 +187,24 @@ func (in *BusinessInformation) DeepCopy() *BusinessInformation { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Conditions) DeepCopyInto(out *Conditions) { + *out = *in + out.HeaderCondition = in.HeaderCondition + out.IPCondition = in.IPCondition + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. +func (in *Conditions) DeepCopy() *Conditions { + if in == nil { + return nil + } + out := new(Conditions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Endpoint) DeepCopyInto(out *Endpoint) { *out = *in @@ -187,6 +221,149 @@ func (in *Endpoint) DeepCopy() *Endpoint { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HeaderCondition) DeepCopyInto(out *HeaderCondition) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderCondition. +func (in *HeaderCondition) DeepCopy() *HeaderCondition { + if in == nil { + return nil + } + out := new(HeaderCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPCondition) DeepCopyInto(out *IPCondition) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPCondition. +func (in *IPCondition) DeepCopy() *IPCondition { + if in == nil { + return nil + } + out := new(IPCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimiting) DeepCopyInto(out *RateLimiting) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimiting. +func (in *RateLimiting) DeepCopy() *RateLimiting { + if in == nil { + return nil + } + out := new(RateLimiting) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RateLimiting) 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 *RateLimitingList) DeepCopyInto(out *RateLimitingList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RateLimiting, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitingList. +func (in *RateLimitingList) DeepCopy() *RateLimitingList { + if in == nil { + return nil + } + out := new(RateLimitingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RateLimitingList) 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 *RateLimitingSpec) DeepCopyInto(out *RateLimitingSpec) { + *out = *in + out.RequestCount = in.RequestCount + out.Bandwidth = in.Bandwidth + out.Conditions = in.Conditions + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitingSpec. +func (in *RateLimitingSpec) DeepCopy() *RateLimitingSpec { + if in == nil { + return nil + } + out := new(RateLimitingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimitingStatus) DeepCopyInto(out *RateLimitingStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitingStatus. +func (in *RateLimitingStatus) DeepCopy() *RateLimitingStatus { + if in == nil { + return nil + } + out := new(RateLimitingStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RequestCount) DeepCopyInto(out *RequestCount) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestCount. +func (in *RequestCount) DeepCopy() *RequestCount { + if in == nil { + return nil + } + out := new(RequestCount) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *URLPattern) DeepCopyInto(out *URLPattern) { *out = *in diff --git a/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.defaults.go b/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.defaults.go new file mode 100644 index 0000000..7985166 --- /dev/null +++ b/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.defaults.go @@ -0,0 +1,16 @@ +// +build !ignore_autogenerated + +// Code generated by defaulter-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + return nil +} diff --git a/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.openapi.go b/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.openapi.go index 56dd904..d508cb1 100644 --- a/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.openapi.go +++ b/apim-operator/pkg/apis/wso2/v1alpha1/zz_generated.openapi.go @@ -13,9 +13,12 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.API": schema_pkg_apis_wso2_v1alpha1_API(ref), - "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.APISpec": schema_pkg_apis_wso2_v1alpha1_APISpec(ref), - "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.APIStatus": schema_pkg_apis_wso2_v1alpha1_APIStatus(ref), + "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.API": schema_pkg_apis_wso2_v1alpha1_API(ref), + "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.APISpec": schema_pkg_apis_wso2_v1alpha1_APISpec(ref), + "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.APIStatus": schema_pkg_apis_wso2_v1alpha1_APIStatus(ref), + "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.RateLimiting": schema_pkg_apis_wso2_v1alpha1_RateLimiting(ref), + "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.RateLimitingSpec": schema_pkg_apis_wso2_v1alpha1_RateLimitingSpec(ref), + "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.RateLimitingStatus": schema_pkg_apis_wso2_v1alpha1_RateLimitingStatus(ref), } } @@ -242,3 +245,113 @@ func schema_pkg_apis_wso2_v1alpha1_APIStatus(ref common.ReferenceCallback) commo Dependencies: []string{}, } } + +func schema_pkg_apis_wso2_v1alpha1_RateLimiting(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RateLimiting is the Schema for the ratelimitings API", + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + 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/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + 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/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.RateLimitingSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.RateLimitingSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_wso2_v1alpha1_RateLimitingSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RateLimitingSpec defines the desired state of RateLimiting", + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "timeUnit": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "unitTime": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + "requestCount": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.RequestCount"), + }, + }, + "stopOnQuotaReach": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "bandwidth": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.Bandwidth"), + }, + }, + "conditions": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.Conditions"), + }, + }, + }, + Required: []string{"type", "timeUnit", "unitTime", "requestCount"}, + }, + }, + Dependencies: []string{ + "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.Bandwidth", "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.Conditions", "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1.RequestCount"}, + } +} + +func schema_pkg_apis_wso2_v1alpha1_RateLimitingStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RateLimitingStatus defines the observed state of RateLimiting", + Properties: map[string]spec.Schema{}, + }, + }, + Dependencies: []string{}, + } +} diff --git a/apim-operator/pkg/controller/add_ratelimiting.go b/apim-operator/pkg/controller/add_ratelimiting.go new file mode 100644 index 0000000..82fa111 --- /dev/null +++ b/apim-operator/pkg/controller/add_ratelimiting.go @@ -0,0 +1,10 @@ +package controller + +import ( + "github.com/apim-crd/apim-operator/pkg/controller/ratelimiting" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, ratelimiting.Add) +} diff --git a/apim-operator/pkg/controller/ratelimiting/ratelimiting_controller.go b/apim-operator/pkg/controller/ratelimiting/ratelimiting_controller.go new file mode 100644 index 0000000..7c7501d --- /dev/null +++ b/apim-operator/pkg/controller/ratelimiting/ratelimiting_controller.go @@ -0,0 +1,214 @@ +package ratelimiting + +import ( + "context" + + wso2v1alpha1 "github.com/apim-crd/apim-operator/pkg/apis/wso2/v1alpha1" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/source" + + "strconv" + "strings" + + mustache "github.com/cbroglie/mustache" +) + +var log = logf.Log.WithName("controller_ratelimiting") + +/** +* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller +* business logic. Delete these comments after modifying this file.* + */ + +// Add creates a new RateLimiting Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + return &ReconcileRateLimiting{client: mgr.GetClient(), scheme: mgr.GetScheme()} +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("ratelimiting-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource RateLimiting + err = c.Watch(&source.Kind{Type: &wso2v1alpha1.RateLimiting{}}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + + // TODO(user): Modify this to be the types you create that are owned by the primary resource + // Watch for changes to secondary resource Pods and requeue the owner RateLimiting + err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &wso2v1alpha1.RateLimiting{}, + }) + if err != nil { + return err + } + + return nil +} + +var _ reconcile.Reconciler = &ReconcileRateLimiting{} + +// ReconcileRateLimiting reconciles a RateLimiting object +type ReconcileRateLimiting struct { + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme +} + +// Reconcile reads that state of the cluster for a RateLimiting object and makes changes based on the state read +// and what is in the RateLimiting.Spec +// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates +// a Pod as an example +// Note: +// The Controller will requeue the Request to be processed again if the returned error is non-nil or +// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. +func (r *ReconcileRateLimiting) Reconcile(request reconcile.Request) (reconcile.Result, error) { + reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) + reqLogger.Info("Reconciling RateLimiting") + + // Fetch the RateLimiting instance + instance := &wso2v1alpha1.RateLimiting{} + err := r.client.Get(context.TODO(), request.NamespacedName, instance) + if err != nil { + if errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + return reconcile.Result{}, err + } + + // GENERATE POLICY CODE USING CRD INSTANCE + + nameArray := strings.Split(instance.ObjectMeta.Name, "-") + name := nameArray[0] + log.Info(name) + + policyType := instance.Spec.Type + if policyType == "subscription" || policyType == "Subscription" { + policyType = "Subscription" + } else if policyType == "application" || policyType == "Application" { + policyType = "Application" + } else { + log.Info("INVALID policy type. Use application or subscription in crd object for type") + return reconcile.Result{}, nil + } + + funcName := "init" + policyType + name + "Policy" + log.Info(funcName) + + var tierType string + var policyKey string + if policyType == "Application" { + tierType = "appTier" + policyKey = "appKey" + } else if policyType == "Subscription" { + tierType = "subscriptionTier" + policyKey = "subscriptionKey" + } + log.Info(tierType) + log.Info(policyKey) + + var unitTime string + if instance.Spec.TimeUnit == "sec" || instance.Spec.TimeUnit == "seconds" { + unitTime = strconv.Itoa(instance.Spec.UnitTime) + } else { + unitTime = strconv.Itoa(instance.Spec.UnitTime * 60000) + } + log.Info(unitTime) + + count := strconv.Itoa(instance.Spec.RequestCount.Limit) + log.Info(count) + + var stopOnQuotaReach string + if policyType == "Subscription" { + stopOnQuotaReach = strconv.FormatBool(instance.Spec.StopOnQuotaReach) + } else { + stopOnQuotaReach = "true" + } + log.Info(stopOnQuotaReach) + + filename := "/usr/local/bin/policy.mustache" + output, err := mustache.RenderFile(filename, map[string]string{"name": name, "funcName": funcName, "tierType": tierType, "policyKey": policyKey, "unitTime": unitTime, "stopOnQuotaReach": stopOnQuotaReach, "count": count}) + + log.Info(output) + + if err != nil { + log.Error(err, "error in rendering ") + } + + //CREATE CONFIG MAP + + confmap, confEr := createConfigMap(output, name, instance) + if confEr != nil { + log.Error(confEr, "Error in config map structure creation") + } + + // Check if this configmap already exists + foundmap := &corev1.ConfigMap{} + err = r.client.Get(context.TODO(), types.NamespacedName{Name: confmap.Name, Namespace: confmap.Namespace}, foundmap) + + if err != nil && errors.IsNotFound(err) { + reqLogger.Info("Creating a new Config map", "confmap.Namespace", confmap.Namespace, "confmap.Name", confmap.Name) + err = r.client.Create(context.TODO(), confmap) + if err != nil { + log.Error(err, "error ") + return reconcile.Result{}, err + } + + // confmap created successfully - don't requeue + return reconcile.Result{}, nil + } else if err != nil { + log.Error(err, "error ") + return reconcile.Result{}, err + } + reqLogger.Info("Map already exists", "confmap.Namespace", foundmap.Namespace, "confmap.Name", foundmap.Name) + reqLogger.Info("Updating Config map", "confmap.Namespace", confmap.Namespace, "confmap.Name", confmap.Name) + err = r.client.Update(context.TODO(), confmap) + if err != nil { + log.Error(err, "error ") + return reconcile.Result{}, err + } + return reconcile.Result{}, nil + +} + +// createConfigMap creates a config file with the generated code +func createConfigMap(output string, name string, cr *wso2v1alpha1.RateLimiting) (*corev1.ConfigMap, error) { + + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.Name + "-configmap", + Namespace: cr.Namespace, + }, + Data: map[string]string{ + "Code": output, + }, + }, nil +}