You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
7.6 KiB
275 lines
7.6 KiB
package k8s
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"path"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
metav1 "github.com/ericchiang/k8s/apis/meta/v1"
|
|
)
|
|
|
|
// Option represents optional call parameters, such as label selectors.
|
|
type Option interface {
|
|
updateURL(base string, v url.Values) string
|
|
updateDelete(r Resource, d *deleteOptions)
|
|
}
|
|
|
|
type optionFunc func(base string, v url.Values) string
|
|
|
|
func (f optionFunc) updateDelete(r Resource, d *deleteOptions) {}
|
|
func (f optionFunc) updateURL(base string, v url.Values) string { return f(base, v) }
|
|
|
|
type deleteOptions struct {
|
|
Kind string `json:"kind"`
|
|
APIVersion string `json:"apiVersion"`
|
|
|
|
GracePeriod *int64 `json:"gracePeriodSeconds,omitempty"`
|
|
Preconditions struct {
|
|
UID string `json:"uid,omitempty"`
|
|
} `json:"preconditions"`
|
|
PropagationPolicy string `json:"propagationPolicy"`
|
|
}
|
|
|
|
// QueryParam can be used to manually set a URL query parameter by name.
|
|
func QueryParam(name, value string) Option {
|
|
return optionFunc(func(base string, v url.Values) string {
|
|
v.Set(name, value)
|
|
return base
|
|
})
|
|
}
|
|
|
|
type deleteOptionFunc func(r Resource, d *deleteOptions)
|
|
|
|
func (f deleteOptionFunc) updateDelete(r Resource, d *deleteOptions) { f(r, d) }
|
|
func (f deleteOptionFunc) updateURL(base string, v url.Values) string { return base }
|
|
|
|
func DeleteAtomic() Option {
|
|
return deleteOptionFunc(func(r Resource, d *deleteOptions) {
|
|
d.Preconditions.UID = *r.GetMetadata().Uid
|
|
})
|
|
}
|
|
|
|
// DeletePropagationOrphan orphans the dependent resources during a delete.
|
|
func DeletePropagationOrphan() Option {
|
|
return deleteOptionFunc(func(r Resource, d *deleteOptions) {
|
|
d.PropagationPolicy = "Orphan"
|
|
})
|
|
}
|
|
|
|
// DeletePropagationBackground deletes the resources and causes the garbage
|
|
// collector to delete dependent resources in the background.
|
|
func DeletePropagationBackground() Option {
|
|
return deleteOptionFunc(func(r Resource, d *deleteOptions) {
|
|
d.PropagationPolicy = "Background"
|
|
})
|
|
}
|
|
|
|
// DeletePropagationForeground deletes the resources and causes the garbage
|
|
// collector to delete dependent resources and wait for all dependents whose
|
|
// ownerReference.blockOwnerDeletion=true. API sever will put the "foregroundDeletion"
|
|
// finalizer on the object, and sets its deletionTimestamp. This policy is
|
|
// cascading, i.e., the dependents will be deleted with Foreground.
|
|
func DeletePropagationForeground() Option {
|
|
return deleteOptionFunc(func(r Resource, d *deleteOptions) {
|
|
d.PropagationPolicy = "Foreground"
|
|
})
|
|
}
|
|
|
|
func DeleteGracePeriod(d time.Duration) Option {
|
|
seconds := int64(d / time.Second)
|
|
return deleteOptionFunc(func(r Resource, d *deleteOptions) {
|
|
d.GracePeriod = &seconds
|
|
})
|
|
}
|
|
|
|
// ResourceVersion causes watch operations to only show changes since
|
|
// a particular version of a resource.
|
|
func ResourceVersion(resourceVersion string) Option {
|
|
return QueryParam("resourceVersion", resourceVersion)
|
|
}
|
|
|
|
// Timeout declares the timeout for list and watch operations. Timeout
|
|
// is only accurate to the second.
|
|
func Timeout(d time.Duration) Option {
|
|
return QueryParam(
|
|
"timeoutSeconds",
|
|
strconv.FormatInt(int64(d/time.Second), 10),
|
|
)
|
|
}
|
|
|
|
// Subresource is a way to interact with a part of an API object without needing
|
|
// permissions on the entire resource. For example, a node isn't able to modify
|
|
// a pod object, but can update the "pods/status" subresource.
|
|
//
|
|
// Common subresources are "status" and "scale".
|
|
//
|
|
// See https://kubernetes.io/docs/reference/api-concepts/
|
|
func Subresource(name string) Option {
|
|
return optionFunc(func(base string, v url.Values) string {
|
|
return base + "/" + name
|
|
})
|
|
}
|
|
|
|
type resourceType struct {
|
|
apiGroup string
|
|
apiVersion string
|
|
name string
|
|
namespaced bool
|
|
}
|
|
|
|
var (
|
|
resources = map[reflect.Type]resourceType{}
|
|
resourceLists = map[reflect.Type]resourceType{}
|
|
)
|
|
|
|
// Resource is a Kubernetes resource, such as a Node or Pod.
|
|
type Resource interface {
|
|
GetMetadata() *metav1.ObjectMeta
|
|
}
|
|
|
|
// Resource is list of common Kubernetes resources, such as a NodeList or
|
|
// PodList.
|
|
type ResourceList interface {
|
|
GetMetadata() *metav1.ListMeta
|
|
}
|
|
|
|
func Register(apiGroup, apiVersion, name string, namespaced bool, r Resource) {
|
|
rt := reflect.TypeOf(r)
|
|
if _, ok := resources[rt]; ok {
|
|
panic(fmt.Sprintf("resource registered twice %T", r))
|
|
}
|
|
resources[rt] = resourceType{apiGroup, apiVersion, name, namespaced}
|
|
}
|
|
|
|
func RegisterList(apiGroup, apiVersion, name string, namespaced bool, l ResourceList) {
|
|
rt := reflect.TypeOf(l)
|
|
if _, ok := resources[rt]; ok {
|
|
panic(fmt.Sprintf("resource registered twice %T", l))
|
|
}
|
|
resourceLists[rt] = resourceType{apiGroup, apiVersion, name, namespaced}
|
|
}
|
|
|
|
func urlFor(endpoint, apiGroup, apiVersion, namespace, resource, name string, options ...Option) string {
|
|
basePath := "apis/"
|
|
if apiGroup == "" {
|
|
basePath = "api/"
|
|
}
|
|
|
|
var p string
|
|
if namespace != "" {
|
|
p = path.Join(basePath, apiGroup, apiVersion, "namespaces", namespace, resource, name)
|
|
} else {
|
|
p = path.Join(basePath, apiGroup, apiVersion, resource, name)
|
|
}
|
|
e := ""
|
|
if strings.HasSuffix(endpoint, "/") {
|
|
e = endpoint + p
|
|
} else {
|
|
e = endpoint + "/" + p
|
|
}
|
|
if len(options) == 0 {
|
|
return e
|
|
}
|
|
|
|
v := url.Values{}
|
|
for _, option := range options {
|
|
e = option.updateURL(e, v)
|
|
}
|
|
if len(v) == 0 {
|
|
return e
|
|
}
|
|
return e + "?" + v.Encode()
|
|
}
|
|
|
|
func urlForPath(endpoint, path string) string {
|
|
if strings.HasPrefix(path, "/") {
|
|
path = path[1:]
|
|
}
|
|
if strings.HasSuffix(endpoint, "/") {
|
|
return endpoint + path
|
|
}
|
|
return endpoint + "/" + path
|
|
}
|
|
|
|
func resourceURL(endpoint string, r Resource, withName bool, options ...Option) (string, error) {
|
|
t, ok := resources[reflect.TypeOf(r)]
|
|
if !ok {
|
|
return "", fmt.Errorf("unregistered type %T", r)
|
|
}
|
|
meta := r.GetMetadata()
|
|
if meta == nil {
|
|
return "", errors.New("resource has no object meta")
|
|
}
|
|
switch {
|
|
case t.namespaced && (meta.Namespace == nil || *meta.Namespace == ""):
|
|
return "", errors.New("no resource namespace provided")
|
|
case !t.namespaced && (meta.Namespace != nil && *meta.Namespace != ""):
|
|
return "", errors.New("resource not namespaced")
|
|
case withName && (meta.Name == nil || *meta.Name == ""):
|
|
return "", errors.New("no resource name provided")
|
|
}
|
|
name := ""
|
|
if withName {
|
|
name = *meta.Name
|
|
}
|
|
namespace := ""
|
|
if t.namespaced {
|
|
namespace = *meta.Namespace
|
|
}
|
|
|
|
return urlFor(endpoint, t.apiGroup, t.apiVersion, namespace, t.name, name, options...), nil
|
|
}
|
|
|
|
func resourceGetURL(endpoint, namespace, name string, r Resource, options ...Option) (string, error) {
|
|
t, ok := resources[reflect.TypeOf(r)]
|
|
if !ok {
|
|
return "", fmt.Errorf("unregistered type %T", r)
|
|
}
|
|
|
|
if !t.namespaced && namespace != "" {
|
|
return "", fmt.Errorf("type not namespaced")
|
|
}
|
|
if t.namespaced && namespace == "" {
|
|
return "", fmt.Errorf("no namespace provided")
|
|
}
|
|
|
|
return urlFor(endpoint, t.apiGroup, t.apiVersion, namespace, t.name, name, options...), nil
|
|
}
|
|
|
|
func resourceListURL(endpoint, namespace string, r ResourceList, options ...Option) (string, error) {
|
|
t, ok := resourceLists[reflect.TypeOf(r)]
|
|
if !ok {
|
|
return "", fmt.Errorf("unregistered type %T", r)
|
|
}
|
|
|
|
if !t.namespaced && namespace != "" {
|
|
return "", fmt.Errorf("type not namespaced")
|
|
}
|
|
|
|
return urlFor(endpoint, t.apiGroup, t.apiVersion, namespace, t.name, "", options...), nil
|
|
}
|
|
|
|
func resourceWatchURL(endpoint, namespace string, r Resource, options ...Option) (string, error) {
|
|
t, ok := resources[reflect.TypeOf(r)]
|
|
if !ok {
|
|
return "", fmt.Errorf("unregistered type %T", r)
|
|
}
|
|
|
|
if !t.namespaced && namespace != "" {
|
|
return "", fmt.Errorf("type not namespaced")
|
|
}
|
|
|
|
url := urlFor(endpoint, t.apiGroup, t.apiVersion, namespace, t.name, "", options...)
|
|
if strings.Contains(url, "?") {
|
|
url = url + "&watch=true"
|
|
} else {
|
|
url = url + "?watch=true"
|
|
}
|
|
return url, nil
|
|
}
|