diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2484634 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +kuberesolver.iml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. \ No newline at end of file diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..ef6b36c --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,19 @@ +# License notice + +Parts of the code are adapted from https://github.com/sercand/kuberesolver + +``` +Copyright 2017 Sercan Degirmenci + + 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. +``` \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d0767f4 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# k8s-client-go + +Minimal Go Kubernetes client based on github.com/sercand/kuberesolver diff --git a/client.go b/client.go new file mode 100644 index 0000000..643f33a --- /dev/null +++ b/client.go @@ -0,0 +1,200 @@ +package client + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/fsnotify/fsnotify" +) + +const ( + serviceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token" + serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" +) + +// Interface is minimal kubernetes Client interface. +type Interface interface { + Do(req *http.Request) (*http.Response, error) + GetRequest(url string) (*http.Request, error) +} + +// NewInCluster creates Client if it is inside Kubernetes. +func NewInCluster() (*Client, error) { + host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT") + if len(host) == 0 || len(port) == 0 { + return nil, fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined") + } + token, err := ioutil.ReadFile(serviceAccountToken) + if err != nil { + return nil, err + } + ca, err := ioutil.ReadFile(serviceAccountCACert) + if err != nil { + return nil, err + } + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(ca) + transport := &http.Transport{TLSClientConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + RootCAs: certPool, + }} + httpClient := &http.Client{Transport: transport, Timeout: time.Nanosecond * 0} + + client := &Client{ + Host: "https://" + net.JoinHostPort(host, port), + Token: string(token), + HttpClient: httpClient, + ResponseDecoderFunc: func(r io.Reader) ResponseDecoder { + return json.NewDecoder(r) + }, + Logger: &DefaultLogger{}, + } + + // Create a new file watcher to listen for new Service Account tokens + watcher, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Op&fsnotify.Write == fsnotify.Write { + token, err := ioutil.ReadFile(serviceAccountToken) + if err == nil { + client.Token = string(token) + } + } + case _, ok := <-watcher.Errors: + if !ok { + return + } + } + } + }() + + err = watcher.Add(serviceAccountToken) + if err != nil { + return nil, err + } + + return client, nil +} + +type Client struct { + Host string + HttpClient *http.Client + Token string + ResponseDecoderFunc func(r io.Reader) ResponseDecoder + Logger Logger +} + +func (kc *Client) GetRequest(ctx context.Context, url string) (*http.Request, error) { + kc.ResponseDecoderFunc = func(r io.Reader) ResponseDecoder { + return json.NewDecoder(r) + } + + if !strings.HasPrefix(url, kc.Host) { + url = fmt.Sprintf("%s/%s", kc.Host, url) + } + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + if len(kc.Token) > 0 { + req.Header.Set("Authorization", "Bearer "+kc.Token) + } + return req, nil +} + +func (kc *Client) Do(req *http.Request) (*http.Response, error) { + return kc.HttpClient.Do(req) +} + +func Get[T Object](kc *Client, ctx context.Context, reqURL string, _ GetOptions) (T, error) { + var t T + u, err := url.Parse(reqURL) + if err != nil { + return t, err + } + req, err := kc.GetRequest(ctx, u.String()) + if err != nil { + return t, err + } + resp, err := kc.Do(req) + if err != nil { + return t, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + errmsg, _ := ioutil.ReadAll(resp.Body) + return t, fmt.Errorf("invalid response code %d for request url %q: %s", resp.StatusCode, reqURL, errmsg) + } + if err := kc.ResponseDecoderFunc(resp.Body).Decode(&t); err != nil { + return t, err + } + return t, err +} + +func Watch[T Object](kc *Client, ctx context.Context, reqURL string, _ ListOptions) (WatchInterface[T], error) { + u, err := url.Parse(reqURL) + if err != nil { + return nil, err + } + req, err := kc.GetRequest(ctx, u.String()) + if err != nil { + return nil, err + } + resp, err := kc.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + defer resp.Body.Close() + errmsg, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("invalid response code %d for request url %q: %s", resp.StatusCode, reqURL, errmsg) + } + + return newStreamWatcher[T](resp.Body, kc.Logger, kc.ResponseDecoderFunc(resp.Body)), nil +} + +func GetEndpoints(kc *Client, ctx context.Context, namespace, targetName string, opts GetOptions) (Endpoints, error) { + reqURL := fmt.Sprintf("%s/api/v1/namespaces/%s/endpoints/%s", kc.Host, namespace, targetName) + return Get[Endpoints](kc, ctx, reqURL, opts) +} + +func WatchEndpoints(kc *Client, ctx context.Context, namespace, targetName string, _ ListOptions) (WatchInterface[Endpoints], error) { + u, err := url.Parse(fmt.Sprintf("%s/api/v1/watch/namespaces/%s/endpoints/%s", kc.Host, namespace, targetName)) + if err != nil { + return nil, err + } + req, err := kc.GetRequest(ctx, u.String()) + if err != nil { + return nil, err + } + resp, err := kc.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + defer resp.Body.Close() + errmsg, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("invalid response code %d for service %s in namespace %s: %s", resp.StatusCode, targetName, namespace, string(errmsg)) + } + return newStreamWatcher[Endpoints](resp.Body, kc.Logger, kc.ResponseDecoderFunc(resp.Body)), nil +} diff --git a/client_test.go b/client_test.go new file mode 100644 index 0000000..06a4ba9 --- /dev/null +++ b/client_test.go @@ -0,0 +1,23 @@ +package client_test + +import ( + "context" + "fmt" + + client "github.com/castai/k8s-client-go" +) + +func ExampleClient() { + kc, err := client.NewInCluster() + if err != nil { + // Handle err + } + + ctx := context.Background() + endpoints, err := client.GetEndpoints(kc, ctx, "kube-system", "kubelet") + if err != nil { + // Handle err + } + + fmt.Printf("%+v", endpoints) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3944384 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/castai/k8s-client-go + +go 1.18 + +require github.com/fsnotify/fsnotify v1.5.4 + +require golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7d51976 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/log.go b/log.go new file mode 100644 index 0000000..84da6a6 --- /dev/null +++ b/log.go @@ -0,0 +1,17 @@ +package client + +import ( + "fmt" + "os" +) + +type Logger interface { + Infof(format string, args ...any) +} + +type DefaultLogger struct { +} + +func (l *DefaultLogger) Infof(format string, args ...any) { + fmt.Fprintf(os.Stdout, format, args) +} diff --git a/stream.go b/stream.go new file mode 100644 index 0000000..68512f8 --- /dev/null +++ b/stream.go @@ -0,0 +1,109 @@ +package client + +import ( + "fmt" + "io" + "sync" +) + +// ResponseDecoder allows to specify custom JSON response decoder. By default, std json decoder is used. +type ResponseDecoder interface { + Decode(v any) error +} + +// WatchInterface can be implemented by anything that knows how to watch and report changes. +type WatchInterface[T Object] interface { + // Stop stops watching. Will close the channel returned by ResultChan(). Releases + // any resources used by the watch. + Stop() + + // ResultChan returns a chan which will receive all the events. If an error occurs + // or Stop() is called, this channel will be closed, in which case the + // watch should be completely cleaned up. + ResultChan() <-chan Event[T] +} + +// StreamWatcher turns any stream for which you can write a Decoder interface +// into a watch.Interface. +type streamWatcher[T Object] struct { + result chan Event[T] + r io.ReadCloser + log Logger + decoder ResponseDecoder + sync.Mutex + stopped bool +} + +// NewStreamWatcher creates a StreamWatcher from the given io.ReadClosers. +func newStreamWatcher[T Object](r io.ReadCloser, log Logger, decoder ResponseDecoder) WatchInterface[T] { + sw := &streamWatcher[T]{ + r: r, + log: log, + decoder: decoder, + result: make(chan Event[T]), + } + go sw.receive() + return sw +} + +// ResultChan implements Interface. +func (sw *streamWatcher[T]) ResultChan() <-chan Event[T] { + return sw.result +} + +// Stop implements Interface. +func (sw *streamWatcher[T]) Stop() { + sw.Lock() + defer sw.Unlock() + if !sw.stopped { + sw.stopped = true + sw.r.Close() + } +} + +// stopping returns true if Stop() was called previously. +func (sw *streamWatcher[T]) stopping() bool { + sw.Lock() + defer sw.Unlock() + return sw.stopped +} + +// receive reads result from the decoder in a loop and sends down the result channel. +func (sw *streamWatcher[T]) receive() { + defer close(sw.result) + defer sw.Stop() + for { + obj, err := sw.Decode() + if err != nil { + // Ignore expected error. + if sw.stopping() { + return + } + switch err { + case io.EOF: + // Watch closed normally. + case io.ErrUnexpectedEOF: + sw.log.Infof("k8s-client-go: unexpected EOF during watch stream event decoding: %v", err) + default: + sw.log.Infof("k8s-client-go: unable to decode an event from the watch stream: %v", err) + } + return + } + sw.result <- obj + } +} + +// Decode blocks until it can return the next object in the writer. Returns an error +// if the writer is closed or an object can't be decoded. +func (sw *streamWatcher[T]) Decode() (Event[T], error) { + var t Event[T] + if err := sw.decoder.Decode(&t); err != nil { + return t, err + } + switch t.Type { + case Added, Modified, Deleted, Error: + return t, nil + default: + return t, fmt.Errorf("got invalid watch event type: %v", t.Type) + } +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..15213c7 --- /dev/null +++ b/types.go @@ -0,0 +1,245 @@ +package client + +import ( + "time" +) + +type EventType string + +const ( + Added EventType = "ADDED" + Modified EventType = "MODIFIED" + Deleted EventType = "DELETED" + Error EventType = "ERROR" +) + +type TypeMeta struct { + // 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 + // +optional + Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` + + // 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 + // +optional + APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"` +} + +type ObjectMeta struct { + // Name must be unique within a namespace. Is required when creating resources, although + // some resources may allow a client to request the generation of an appropriate name + // automatically. Name is primarily intended for creation idempotence and configuration + // definition. + // Cannot be updated. + // More info: http://kubernetes.io/docs/user-guide/identifiers#names + // +optional + Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` + + // GenerateName is an optional prefix, used by the server, to generate a unique + // name ONLY IF the Name field has not been provided. + // If this field is used, the name returned to the client will be different + // than the name passed. This value will also be combined with a unique suffix. + // The provided value has the same validation rules as the Name field, + // and may be truncated by the length of the suffix required to make the value + // unique on the server. + // + // If this field is specified and the generated name exists, the server will return a 409. + // + // Applied only if Name is not specified. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency + // +optional + GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"` + + // Namespace defines the space within which each name must be unique. An empty namespace is + // equivalent to the "default" namespace, but "default" is the canonical representation. + // Not all objects are required to be scoped to a namespace - the value of this field for + // those objects will be empty. + // + // Must be a DNS_LABEL. + // Cannot be updated. + // More info: http://kubernetes.io/docs/user-guide/namespaces + // +optional + Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"` + + // Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. + // +optional + SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"` + + // UID is the unique in time and space value for this object. It is typically generated by + // the server on successful creation of a resource and is not allowed to change on PUT + // operations. + // + // Populated by the system. + // Read-only. + // More info: http://kubernetes.io/docs/user-guide/identifiers#uids + // +optional + UID string `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"` + + // An opaque value that represents the internal version of this object that can + // be used by clients to determine when objects have changed. May be used for optimistic + // concurrency, change detection, and the watch operation on a resource or set of resources. + // Clients must treat these values as opaque and passed unmodified back to the server. + // They may only be valid for a particular resource or set of resources. + // + // Populated by the system. + // Read-only. + // Value must be treated as opaque by clients and . + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + // +optional + ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"` + + // A sequence number representing a specific generation of the desired state. + // Populated by the system. Read-only. + // +optional + Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"` + + // CreationTimestamp is a timestamp representing the server time when this object was + // created. It is not guaranteed to be set in happens-before order across separate operations. + // Clients may not set this value. It is represented in RFC3339 form and is in UTC. + // + // Populated by the system. + // Read-only. + // Null for lists. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + CreationTimestamp time.Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"` + + // DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This + // field is set by the server when a graceful deletion is requested by the user, and is not + // directly settable by a client. The resource is expected to be deleted (no longer visible + // from resource lists, and not reachable by name) after the time in this field, once the + // finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. + // Once the deletionTimestamp is set, this value may not be unset or be set further into the + // future, although it may be shortened or the resource may be deleted prior to this time. + // For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react + // by sending a graceful termination signal to the containers in the pod. After that 30 seconds, + // the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, + // remove the pod from the API. In the presence of network partitions, this object may still + // exist after this timestamp, until an administrator or automated process can determine the + // resource is fully terminated. + // If not set, graceful deletion of the object has not been requested. + // + // Populated by the system when a graceful deletion is requested. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"` + + // Number of seconds allowed for this object to gracefully terminate before + // it will be removed from the system. Only set when deletionTimestamp is also set. + // May only be shortened. + // Read-only. + // +optional + DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"` + + // Map of string keys and values that can be used to organize and categorize + // (scope and select) objects. May match selectors of replication controllers + // and services. + // More info: http://kubernetes.io/docs/user-guide/labels + // +optional + Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"` + + // Annotations is an unstructured key value map stored with a resource that may be + // set by external tools to store and retrieve arbitrary metadata. They are not + // queryable and should be preserved when modifying objects. + // More info: http://kubernetes.io/docs/user-guide/annotations + // +optional + Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"` + + // List of objects depended by this object. If ALL objects in the list have + // been deleted, this object will be garbage collected. If this object is managed by a controller, + // then an entry in this list will point to this controller, with the controller field set to true. + // There cannot be more than one managing controller. + // +optional + // +patchMergeKey=uid + // +patchStrategy=merge + OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"` + + Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"` +} + +type OwnerReference struct { + // API version of the referent. + APIVersion string `json:"apiVersion" protobuf:"bytes,5,opt,name=apiVersion"` + // Kind of the referent. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"` + // Name of the referent. + // More info: http://kubernetes.io/docs/user-guide/identifiers#names + Name string `json:"name" protobuf:"bytes,3,opt,name=name"` + // UID of the referent. + // More info: http://kubernetes.io/docs/user-guide/identifiers#uids + UID string `json:"uid" protobuf:"bytes,4,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"` + // If true, this reference points to the managing controller. + // +optional + Controller *bool `json:"controller,omitempty" protobuf:"varint,6,opt,name=controller"` + // If true, AND if the owner has the "foregroundDeletion" finalizer, then + // the owner cannot be deleted from the key-value store until this + // reference is removed. + // See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion + // for how the garbage collector interacts with this field and enforces the foreground deletion. + // Defaults to false. + // To set this field, a user needs "delete" permission of the owner, + // otherwise 422 (Unprocessable Entity) will be returned. + // +optional + BlockOwnerDeletion *bool `json:"blockOwnerDeletion,omitempty" protobuf:"varint,7,opt,name=blockOwnerDeletion"` +} + +// GetOptions is reserved to be implemented. +type GetOptions struct { +} + +// ListOptions is reserved to be implemented. +type ListOptions struct { +} + +// Object is kubernetes object. +type Object interface { + GetObjectMeta() ObjectMeta + GetTypeMeta() TypeMeta +} + +// Event represents a single event to a watched resource. +type Event[T Object] struct { + Type EventType `json:"type"` + Object T `json:"object"` +} + +type Endpoints struct { + TypeMeta `json:",inline"` + ObjectMeta `json:"metadata,omitempty"` + Subsets []Subset `json:"subsets"` +} + +func (o Endpoints) GetObjectMeta() ObjectMeta { + return o.ObjectMeta +} + +func (o Endpoints) GetTypeMeta() TypeMeta { + return o.TypeMeta +} + +type Subset struct { + Addresses []Address `json:"addresses"` + Ports []Port `json:"ports"` +} + +type Address struct { + IP string `json:"ip"` + TargetRef *ObjectReference `json:"targetRef,omitempty"` +} + +type ObjectReference struct { + Kind string `json:"kind"` + Name string `json:"name"` + Namespace string `json:"namespace"` +} +type Port struct { + Name string `json:"name"` + Port int `json:"port"` +}