diff --git a/deploy/crds/ohmyglb.absa.oss_gslbs_crd.yaml b/deploy/crds/ohmyglb.absa.oss_gslbs_crd.yaml new file mode 100644 index 0000000000..d8f5facd51 --- /dev/null +++ b/deploy/crds/ohmyglb.absa.oss_gslbs_crd.yaml @@ -0,0 +1,195 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: gslbs.ohmyglb.absa.oss +spec: + group: ohmyglb.absa.oss + names: + kind: Gslb + listKind: GslbList + plural: gslbs + singular: gslb + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: Gslb is the Schema for the gslbs 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/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: + description: GslbSpec defines the desired state of Gslb + properties: + ingress: + description: '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-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + properties: + backend: + description: A default backend capable of servicing requests that + don't match any rule. At least one of 'backend' or 'rules' must + be specified. This field is optional to allow the loadbalancer + controller or defaulting logic to specify a global default. + properties: + serviceName: + description: Specifies the name of the referenced service. + type: string + servicePort: + anyOf: + - type: string + - type: integer + description: Specifies the port of the referenced service. + required: + - serviceName + - servicePort + type: object + rules: + description: A list of host rules used to configure the Ingress. + If unspecified, or no rule matches, all traffic is sent to the + default backend. + items: + description: IngressRule represents the rules mapping the paths + under a specified host to the related backend services. Incoming + requests are first evaluated for a host match, then routed to + the backend associated with the matching IngressRuleValue. + properties: + host: + description: "Host is the fully qualified domain name of a + network host, as defined by RFC 3986. Note the following + deviations from the \"host\" part of the URI as defined + in the RFC: 1. IPs are not allowed. Currently an IngressRuleValue + can only apply to the \t IP in the Spec of the parent Ingress. + 2. The `:` delimiter is not respected because ports are + not allowed. \t Currently the port of an Ingress is implicitly + :80 for http and \t :443 for https. Both these may change + in the future. Incoming requests are matched against the + host before the IngressRuleValue. If the host is unspecified, + the Ingress routes all traffic based on the specified IngressRuleValue." + type: string + http: + description: 'HTTPIngressRuleValue is a list of http selectors + pointing to backends. In the example: http:///? + -> backend where where parts of the url correspond to RFC + 3986, this resource will be used to match against everything + after the last ''/'' and before the first ''?'' or ''#''.' + properties: + paths: + description: A collection of paths that map requests to + backends. + items: + description: HTTPIngressPath associates a path regex + with a backend. Incoming urls matching the path are + forwarded to the backend. + properties: + backend: + description: Backend defines the referenced service + endpoint to which the traffic will be forwarded + to. + properties: + serviceName: + description: Specifies the name of the referenced + service. + type: string + servicePort: + anyOf: + - type: string + - type: integer + description: Specifies the port of the referenced + service. + required: + - serviceName + - servicePort + type: object + path: + description: Path is an extended POSIX regex as + defined by IEEE Std 1003.1, (i.e this follows + the egrep/unix syntax, not the perl syntax) matched + against the path of an incoming request. Currently + it can contain characters disallowed from the + conventional "path" part of a URL as defined by + RFC 3986. Paths must begin with a '/'. If unspecified, + the path defaults to a catch all sending traffic + to the backend. + type: string + required: + - backend + type: object + type: array + required: + - paths + type: object + type: object + type: array + tls: + description: TLS configuration. Currently the Ingress only supports + a single TLS port, 443. If multiple members of this list specify + different hosts, they will be multiplexed on the same port according + to the hostname specified through the SNI TLS extension, if the + ingress controller fulfilling the ingress supports SNI. + items: + description: IngressTLS describes the transport layer security + associated with an Ingress. + properties: + hosts: + description: Hosts are a list of hosts included in the TLS + certificate. The values in this list must match the name/s + used in the tlsSecret. Defaults to the wildcard host setting + for the loadbalancer controller fulfilling this Ingress, + if left unspecified. + items: + type: string + type: array + secretName: + description: SecretName is the name of the secret used to + terminate SSL traffic on 443. Field is left optional to + allow SSL routing based on SNI hostname alone. If the SNI + host in a listener conflicts with the "Host" header field + used by an IngressRule, the SNI host is used for termination + and value of the Host header is used for routing. + type: string + type: object + type: array + type: object + strategy: + type: string + required: + - ingress + - strategy + type: object + status: + description: GslbStatus defines the observed state of Gslb + properties: + managedHosts: + description: '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-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + items: + type: string + type: array + serviceHealth: + additionalProperties: + type: string + type: object + required: + - managedHosts + - serviceHealth + type: object + type: object + version: v1beta1 + versions: + - name: v1beta1 + served: true + storage: true diff --git a/deploy/crds/ohmyglb.absa.oss_v1beta1_gslb_cr.yaml b/deploy/crds/ohmyglb.absa.oss_v1beta1_gslb_cr.yaml new file mode 100644 index 0000000000..fa0ad38146 --- /dev/null +++ b/deploy/crds/ohmyglb.absa.oss_v1beta1_gslb_cr.yaml @@ -0,0 +1,15 @@ +apiVersion: ohmyglb.absa.oss/v1beta1 +kind: Gslb +metadata: + name: example-gslb +spec: + ingress: + rules: + - host: app.cloud.absa.internal # This is the GSLB enabled host that clients would use + http: # This section mirrors the same structure as that of an Ingress resource and will be used verbatim when creating the corresponding Ingress resource that will match the GSLB host + paths: + - backend: + serviceName: app + servicePort: http + path: / + strategy: roundRobin # Use a round robin load balancing strategy, when deciding which downstream clusters to route clients too diff --git a/deploy/role.yaml b/deploy/role.yaml index 094f09e17e..21eafd249b 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -58,5 +58,13 @@ rules: - ohmyglb.absa.oss resources: - '*' + - gslbs verbs: - '*' +- apiGroups: + - extensions + resources: + - ingresses + verbs: + - get + - list diff --git a/go.sum b/go.sum index 5f02aec991..5f893f8407 100644 --- a/go.sum +++ b/go.sum @@ -252,6 +252,7 @@ github.com/heketi/heketi v0.0.0-20181109135656-558b29266ce0/go.mod h1:bB9ly3Rchc github.com/heketi/rest v0.0.0-20180404230133-aa6a65207413/go.mod h1:BeS3M108VzVlmAue3lv2WcGuPAX94/KN63MUURzbYSI= github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4= github.com/heketi/utils v0.0.0-20170317161834-435bc5bdfa64/go.mod h1:RYlF4ghFZPPmk2TC5REt5OFwvfb6lzxFWrTWB+qs28s= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/iancoleman/strcase v0.0.0-20180726023541-3605ed457bf7/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= @@ -342,12 +343,14 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.2-0.20180831124310-ae19f1b56d53/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -379,6 +382,7 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v0.0.0-20160930220758-4d0e916071f6/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= @@ -458,6 +462,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A= @@ -618,6 +623,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3/go.mod h1:l0 gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.0.0-20180411045311-89060dee6a84/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= @@ -631,6 +637,7 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.0.0-20190918195907-bd6ac527cfd2 h1:bkwe5LsuANqyOwsBng5Qc4S91D2Tv0JHctAztt3YTQs= k8s.io/api v0.0.0-20190918195907-bd6ac527cfd2/go.mod h1:AOxZTnaXR/xiarlQL0JUfwQPxjmKDvVYoRp58cA7lUo= +k8s.io/apiextensions-apiserver v0.0.0-20190918201827-3de75813f604 h1:Kl/sh+wWzYK2hWFZtwvuFECup1SbE2kXfMnhGZsoO5M= k8s.io/apiextensions-apiserver v0.0.0-20190918201827-3de75813f604/go.mod h1:7H8sjDlWQu89yWB3FhZfsLyRCRLuoXoCoY5qtwW1q6I= k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d h1:7Kns6qqhMAQWvGkxYOLSLRZ5hJO0/5pcE5lPGP2fxUw= k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d/go.mod h1:3jediapYqJ2w1BFw7lAZPCx7scubsTfosqHkhXCWJKw= @@ -697,6 +704,7 @@ sigs.k8s.io/controller-tools v0.2.2/go.mod h1:8SNGuj163x/sMwydREj7ld5mIMJu1cDanI sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkWhVrs= sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/pkg/apis/ohmyglb/v1beta1/gslb_types.go b/pkg/apis/ohmyglb/v1beta1/gslb_types.go new file mode 100644 index 0000000000..452e2aa0be --- /dev/null +++ b/pkg/apis/ohmyglb/v1beta1/gslb_types.go @@ -0,0 +1,57 @@ +package v1beta1 + +import ( + v1beta1 "k8s.io/api/extensions/v1beta1" + 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. + +// GslbSpec defines the desired state of Gslb +// +k8s:openapi-gen=true +type GslbSpec 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-v1.book.kubebuilder.io/beyond_basics/generating_crd.html + Ingress v1beta1.IngressSpec `json:"ingress"` + Strategy string `json:"strategy"` +} + +// GslbStatus defines the observed state of Gslb +// +k8s:openapi-gen=true +type GslbStatus 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-v1.book.kubebuilder.io/beyond_basics/generating_crd.html + // +listType=set + ManagedHosts []string `json:"managedHosts"` + ServiceHealth map[string]string `json:"serviceHealth"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Gslb is the Schema for the gslbs API +// +k8s:openapi-gen=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:path=gslbs,scope=Namespaced +type Gslb struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GslbSpec `json:"spec,omitempty"` + Status GslbStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// GslbList contains a list of Gslb +type GslbList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Gslb `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Gslb{}, &GslbList{}) +} diff --git a/pkg/apis/ohmyglb/v1beta1/zz_generated.deepcopy.go b/pkg/apis/ohmyglb/v1beta1/zz_generated.deepcopy.go index 5b0d213d6c..c4c3439539 100644 --- a/pkg/apis/ohmyglb/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/ohmyglb/v1beta1/zz_generated.deepcopy.go @@ -8,6 +8,67 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Gslb) DeepCopyInto(out *Gslb) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Gslb. +func (in *Gslb) DeepCopy() *Gslb { + if in == nil { + return nil + } + out := new(Gslb) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Gslb) 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 *GslbList) DeepCopyInto(out *GslbList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Gslb, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GslbList. +func (in *GslbList) DeepCopy() *GslbList { + if in == nil { + return nil + } + out := new(GslbList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GslbList) 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 *GslbResolver) DeepCopyInto(out *GslbResolver) { *out = *in @@ -105,3 +166,48 @@ func (in *GslbResolverStatus) DeepCopy() *GslbResolverStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GslbSpec) DeepCopyInto(out *GslbSpec) { + *out = *in + in.Ingress.DeepCopyInto(&out.Ingress) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GslbSpec. +func (in *GslbSpec) DeepCopy() *GslbSpec { + if in == nil { + return nil + } + out := new(GslbSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GslbStatus) DeepCopyInto(out *GslbStatus) { + *out = *in + if in.ManagedHosts != nil { + in, out := &in.ManagedHosts, &out.ManagedHosts + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ServiceHealth != nil { + in, out := &in.ServiceHealth, &out.ServiceHealth + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GslbStatus. +func (in *GslbStatus) DeepCopy() *GslbStatus { + if in == nil { + return nil + } + out := new(GslbStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/ohmyglb/v1beta1/zz_generated.openapi.go b/pkg/apis/ohmyglb/v1beta1/zz_generated.openapi.go index 25689c6b9c..3bc868f068 100644 --- a/pkg/apis/ohmyglb/v1beta1/zz_generated.openapi.go +++ b/pkg/apis/ohmyglb/v1beta1/zz_generated.openapi.go @@ -11,9 +11,56 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ + "./pkg/apis/ohmyglb/v1beta1.Gslb": schema_pkg_apis_ohmyglb_v1beta1_Gslb(ref), "./pkg/apis/ohmyglb/v1beta1.GslbResolver": schema_pkg_apis_ohmyglb_v1beta1_GslbResolver(ref), "./pkg/apis/ohmyglb/v1beta1.GslbResolverSpec": schema_pkg_apis_ohmyglb_v1beta1_GslbResolverSpec(ref), "./pkg/apis/ohmyglb/v1beta1.GslbResolverStatus": schema_pkg_apis_ohmyglb_v1beta1_GslbResolverStatus(ref), + "./pkg/apis/ohmyglb/v1beta1.GslbSpec": schema_pkg_apis_ohmyglb_v1beta1_GslbSpec(ref), + "./pkg/apis/ohmyglb/v1beta1.GslbStatus": schema_pkg_apis_ohmyglb_v1beta1_GslbStatus(ref), + } +} + +func schema_pkg_apis_ohmyglb_v1beta1_Gslb(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Gslb is the Schema for the gslbs API", + Type: []string{"object"}, + 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("./pkg/apis/ohmyglb/v1beta1.GslbSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Ref: ref("./pkg/apis/ohmyglb/v1beta1.GslbStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "./pkg/apis/ohmyglb/v1beta1.GslbSpec", "./pkg/apis/ohmyglb/v1beta1.GslbStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -114,3 +161,78 @@ func schema_pkg_apis_ohmyglb_v1beta1_GslbResolverStatus(ref common.ReferenceCall }, } } + +func schema_pkg_apis_ohmyglb_v1beta1_GslbSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GslbSpec defines the desired state of Gslb", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ingress": { + SchemaProps: spec.SchemaProps{ + Description: "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-v1.book.kubebuilder.io/beyond_basics/generating_crd.html", + Ref: ref("k8s.io/api/extensions/v1beta1.IngressSpec"), + }, + }, + "strategy": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"ingress", "strategy"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/extensions/v1beta1.IngressSpec"}, + } +} + +func schema_pkg_apis_ohmyglb_v1beta1_GslbStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GslbStatus defines the observed state of Gslb", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "managedHosts": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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-v1.book.kubebuilder.io/beyond_basics/generating_crd.html", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "serviceHealth": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"managedHosts", "serviceHealth"}, + }, + }, + } +} diff --git a/pkg/controller/add_gslb.go b/pkg/controller/add_gslb.go new file mode 100644 index 0000000000..a75e5ba0e2 --- /dev/null +++ b/pkg/controller/add_gslb.go @@ -0,0 +1,10 @@ +package controller + +import ( + "github.com/AbsaOSS/ohmyglb/pkg/controller/gslb" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, gslb.Add) +} diff --git a/pkg/controller/gslb/gslb_controller.go b/pkg/controller/gslb/gslb_controller.go new file mode 100644 index 0000000000..b80de95619 --- /dev/null +++ b/pkg/controller/gslb/gslb_controller.go @@ -0,0 +1,118 @@ +package gslb + +import ( + "context" + + ohmyglbv1beta1 "github.com/AbsaOSS/ohmyglb/pkg/apis/ohmyglb/v1beta1" + v1beta1 "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +var log = logf.Log.WithName("controller_gslb") + +/** +* 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 Gslb 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 &ReconcileGslb{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("gslb-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource Gslb + err = c.Watch(&source.Kind{Type: &ohmyglbv1beta1.Gslb{}}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + + // Watch for changes to secondary resource Ingress + err = c.Watch(&source.Kind{Type: &v1beta1.Ingress{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &ohmyglbv1beta1.Gslb{}, + }) + if err != nil { + return err + } + + return nil +} + +// blank assignment to verify that ReconcileGslb implements reconcile.Reconciler +var _ reconcile.Reconciler = &ReconcileGslb{} + +// ReconcileGslb reconciles a Gslb object +type ReconcileGslb 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 Gslb object and makes changes based on the state read +// and what is in the Gslb.Spec +// 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 *ReconcileGslb) Reconcile(request reconcile.Request) (reconcile.Result, error) { + reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) + reqLogger.Info("Reconciling Gslb") + + // Fetch the Gslb instance + gslb := &ohmyglbv1beta1.Gslb{} + err := r.client.Get(context.TODO(), request.NamespacedName, gslb) + 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 + } + + var result *reconcile.Result + + // == Ingress ========== + result, err = r.ensureIngress( + request, + gslb, + r.gslbIngress(gslb)) + if result != nil { + return *result, err + } + + // == Status = + err = r.updateGslbStatus(gslb) + if err != nil { + // Requeue the request + return reconcile.Result{}, err + } + + // == Finish ========== + // Everything went fine, don't requeue + return reconcile.Result{}, nil +} diff --git a/pkg/controller/gslb/ingress.go b/pkg/controller/gslb/ingress.go new file mode 100644 index 0000000000..36da96f82a --- /dev/null +++ b/pkg/controller/gslb/ingress.go @@ -0,0 +1,58 @@ +package gslb + +import ( + "context" + + ohmyglbv1beta1 "github.com/AbsaOSS/ohmyglb/pkg/apis/ohmyglb/v1beta1" + v1beta1 "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +func (r *ReconcileGslb) gslbIngress(gslb *ohmyglbv1beta1.Gslb) *v1beta1.Ingress { + ingress := &v1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: gslb.Name, + Namespace: gslb.Namespace, + }, + Spec: gslb.Spec.Ingress, + } + + controllerutil.SetControllerReference(gslb, ingress, r.scheme) + return ingress +} + +func (r *ReconcileGslb) ensureIngress(request reconcile.Request, + instance *ohmyglbv1beta1.Gslb, + i *v1beta1.Ingress, +) (*reconcile.Result, error) { + found := &v1beta1.Ingress{} + err := r.client.Get(context.TODO(), types.NamespacedName{ + Name: instance.Name, + Namespace: instance.Namespace, + }, found) + if err != nil && errors.IsNotFound(err) { + + // Create the service + log.Info("Creating a new Ingress", "Ingress.Namespace", i.Namespace, "Ingress.Name", i.Name) + err = r.client.Create(context.TODO(), i) + + if err != nil { + // Creation failed + log.Error(err, "Failed to create new Ingress", "Ingress.Namespace", i.Namespace, "Ingress.Name", i.Name) + return &reconcile.Result{}, err + } else { + // Creation was successful + return nil, nil + } + } else if err != nil { + // Error that isn't due to the service not existing + log.Error(err, "Failed to get Ingress") + return &reconcile.Result{}, err + } + + return nil, nil +} diff --git a/pkg/controller/gslb/status.go b/pkg/controller/gslb/status.go new file mode 100644 index 0000000000..2b6fef0f54 --- /dev/null +++ b/pkg/controller/gslb/status.go @@ -0,0 +1,65 @@ +package gslb + +import ( + "context" + + ohmyglbv1beta1 "github.com/AbsaOSS/ohmyglb/pkg/apis/ohmyglb/v1beta1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func (r *ReconcileGslb) updateGslbStatus(gslb *ohmyglbv1beta1.Gslb) error { + gslb.Status.ManagedHosts = getGslbManagedHosts(gslb) + serviceHealth, err := r.getServiceHealthStatus(gslb) + if err != nil { + return err + } + gslb.Status.ServiceHealth = serviceHealth + err = r.client.Status().Update(context.TODO(), gslb) + return err +} + +func getGslbManagedHosts(gslb *ohmyglbv1beta1.Gslb) []string { + var hosts []string + for _, rule := range gslb.Spec.Ingress.Rules { + hosts = append(hosts, rule.Host) + } + return hosts +} + +func (r *ReconcileGslb) getServiceHealthStatus(gslb *ohmyglbv1beta1.Gslb) (map[string]string, error) { + serviceHealth := make(map[string]string) + for _, rule := range gslb.Spec.Ingress.Rules { + for _, path := range rule.HTTP.Paths { + service := &corev1.Service{} + finder := client.ObjectKey{ + Namespace: gslb.Namespace, + Name: path.Backend.ServiceName, + } + err := r.client.Get(context.TODO(), finder, service) + if err != nil { + if errors.IsNotFound(err) { + serviceHealth[path.Backend.ServiceName] = "NotFound" + continue + } + return serviceHealth, err + } + endpointsList := &corev1.EndpointsList{} + opts := []client.ListOption{ + client.InNamespace(gslb.Namespace), + client.MatchingLabels(service.Spec.Selector), + } + err = r.client.List(context.TODO(), endpointsList, opts...) + if err != nil { + return serviceHealth, err + } + if len(endpointsList.Items) > 0 { + serviceHealth[service.Name] = "Healthy" + } else { + serviceHealth[service.Name] = "Unhealthy" + } + } + } + return serviceHealth, nil +}